10

我正在为嵌入式系统编写一些 C++ 代码。代码使用的 I/O 接口要求每条消息的大小(以字节为单位)是 2 的幂。现在,代码做了这样的事情(在几个地方):

#pragma pack(1)
struct Message
{
   struct internal_
   {
      unsigned long member1;
      unsigned long member2;
      unsigned long member3;
      /* more members */
   } internal;
   char pad[64-sizeof(internal_)];
};
#pragma pack()

我第一次尝试在 64 位 Fedora 上编译代码,这里long是 64 位。在这种情况下,sizeof(internal_)大于 64,数组大小表达式下溢,编译器抱怨数组太大。

理想情况下,我希望能够编写一个宏来获取结构的大小并在编译时评估填充数组的所需大小,以便将结构的大小四舍五入为 2 的幂。

我查看了Bit Twiddling Hacks页面,但我不知道那里的任何技术是否真的可以在宏中实现,以便在编译时进行评估。

这个问题还有其他解决方案吗?还是我应该延续这个问题,只是将神奇的 64 更改为神奇的 128?

4

11 回答 11

15

使用模板元程序。(根据评论进行编辑)。

#include <iostream>
#include <ostream>
using namespace std;

template <int N>
struct P
{
    enum { val = P<N/2>::val * 2 };
};

template <>
struct P<0>
{
    enum { val = 1 };
};

template <class T>
struct PadSize
{
    enum { val = P<sizeof (T) - 1>::val - sizeof (T) }; 
};

template <class T, int N>
struct PossiblyPadded
{
    T       payload;
    char    pad[N]; 
};

template <class T>
struct PossiblyPadded<T, 0>
{
    T       payload;
};

template <class T>
struct Holder : public PossiblyPadded<T, PadSize<T>::val>
{
};


int main()
{
    typedef char Arr[6];

    Holder<Arr> holder;
    cout << sizeof holder.payload << endl;

    // Next line fails to compile if sizeof (Arr) is a power of 2
    // but holder.payload always exists
    cout << sizeof holder.pad << endl;
}
于 2009-08-06T16:26:12.157 回答
6

可能最明显的方法是只使用三元运算符:

#define LOG2_CONST(n) ((n) <= 1 ? 0 :
                      ((n) <= 2 ? 1 :
                      ((n) <= 4 ? 2 :
                      /* ... */
                      ))))))))))))))))))))))))))))))
#define PADDED_STRUCT(ResultName, BaseName) \
  typedef union { BaseName data; char pad[1 << LOG2_CONST(sizeof(BaseName))]; } ResultName
于 2009-08-06T16:00:09.950 回答
5

为什么不使用工会?

union Message
{
    struct internal_
    {
        unsigned long member1;
        /* more members */
    };
    char[64];
};

或者更好的是使用匿名结构

union Message
{
    struct
    {
        unsigned long member1;
        /* more members */
    };
    char[64];
};

所以你可以像这样访问成员:Message.member1;

编辑:显然这并不能解决您大于 64 的问题,但提供了一种更清洁的填充方式。

于 2009-08-06T15:58:53.930 回答
4

解决此问题的一种方法是将硬编码的 64 替换为 size(long) 的倍数,将填充变成如下所示:

char pad[4*sizeof(unsigned long) - sizeof(internal_)];

它很丑,但它应该可以移植到 64 位。

也就是说,要求消息大小为 2 的幂的 API 听起来有点奇怪,就像一个设计问题。要求大小为偶数是有道理的,因为在某些处理器上访问奇数地址上的数据会付出相当大的代价,但您的#pragma 包几乎使这不可避免。

于 2009-08-06T16:01:55.827 回答
4

不如只在发送和接收消息函数周围编写一个小包装器来处理任何大小的消息,然后他们只需分配一个更大的缓冲区(下一个 2 的幂)并清除它,将结构复制到开头并发送它。

于 2009-08-06T16:15:02.273 回答
2

您已经在使用#pragma pack,我不知道您具体使用的是什么编译器,但是您应该查看它们是否支持控制对齐/填充的 pack 参数,然后您可以完全摆脱填充字段。我知道MSVC 的版本pragma pack支持这一点,GCC 的.

于 2009-08-06T17:13:24.123 回答
2

您可以将其宏化如下(对于 32 位架构):

#define align_step(N, shift) ((N) | ((N) >> shift))
#define align_up(N) (align_step(align_step(align_step(align_step(align_step((N)-1, 1), 2), 4), 8), 16) + 1)
#define alignment_padding(N) (align_up((N)) - (N))

然后您可以使用联合技巧或其他方式应用它。在您的示例中:

#pragma pack(1)
struct Message
{
   struct internal_
   {
      unsigned long member1;
      unsigned long member2;
      unsigned long member3;
      /* more members */
   } internal;
   char pad[alignment_padding(sizeof(internal_))];
};
#pragma pack()
于 2009-08-06T18:37:15.607 回答
1

您可以使用模板获得结构大小的编译时常量,四舍五入为 2 的幂:

template<int N, int C = 1>
struct Recurse
{
    enum {result = Recurse<N/2, C*2>::result};
};

template<int C>
struct Recurse<0, C>
{
    enum {result = C};
};

template<typename T>
struct Calc
{
    enum {size = Recurse<sizeof(Test)-1>::result};
};

struct Test
{
    int a;
    double b;
    double c;
};

int main()
{
    std::cout << sizeof(Test) << " -> " << Calc<Test>::size << std::endl;
    return 0;
}

填充值应该很容易。

于 2009-08-06T16:38:11.433 回答
0
union Message
{
    struct
    {
        unsigned long member1;
        unsigned long member2; //...
    };
    char pad[1 << 5]; //change 5 to whatever size you need...
};

会干净一些。

于 2009-08-06T16:24:19.090 回答
0

我喜欢Niki 的回答,尤其是带有匿名结构的部分。

答案没有解决的一件事是大于 64 字节的问题,但这可以通过在sizeof(long)==8时有条件地声明char[128]结构成员并在其他情况下声明char[64]来解决。

于 2009-08-06T16:28:32.637 回答
0

还有另一个模板解决方案(从fizzer 大量抢劫):

#include <iostream>
#include <ostream>
using namespace std;

template <int TSize, int PSize = 1, bool = false>
struct PadSize
{
  static const int val =
    ( PadSize <    TSize, (PSize*2), (TSize <= (PSize*2)  )    > :: val );
};

template < int TSize, int PSize>
struct PadSize <TSize, PSize, true>  // As soon as TSize is <= to PSize
{
  static const int val = PSize;
};

int main()
{
    typedef char Arr[8];
    char pad[ PadSize <sizeof(Arr)>::val  ];

    cout << sizeof pad << endl;
}

我的方法是简单地将填充大小增加一倍,直到它至少与类型大小一样大。

于 2009-08-06T17:08:31.120 回答