C++中的struct padding

Bar*_*uch 51 c++ struct

如果我有一个structC++,有没有办法安全地读/写一个跨平台/编译器兼容的文件?

因为如果我理解正确,每个编译器"填充"基于目标平台不同.

Naw*_*waz 49

不,那是不可能的.这是因为在二进制级别缺乏C++的标准化.

Don Box写道(引用他的书Essential COM,COM作为更好的C++章节)

C++和可移植性


一旦决定将C++类作为DLL分发,就会遇到C++的一个基本弱点,即在二进制级别缺乏标准化.虽然ISO/ANSI C++草案工作文件试图编写哪些程序将编译以及运行它们的语义效果是什么,但它没有尝试标准化C++的二进制运行时模型.这个问题第一次变得明显,当客户端试图从C++开发环境(而不是用于构建FastString DLL的环境)链接FastString DLL的导入库时.

不同的编译器以不同的方式完成结构填充.即使您使用相同的编译器,结构的打包对齐也可能因您正在使用的pragma包而不同.

不仅如果你编写两个结构完全相同的结构,唯一的区别是它们声明的顺序是不同的,那么每个结构的大小可以(通常是)不同.

例如,看到这个,

struct A
{
   char c;
   char d;
   int i;
};

struct B
{
   char c;
   int i;
   char d;
};

int main() {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
}
Run Code Online (Sandbox Code Playgroud)

用它编译gcc-4.3.4,你得到这个输出:

8
12
Run Code Online (Sandbox Code Playgroud)

也就是说,即使两个结构都具有相同的成员,大小也是不同的!

Ideone的代码:http://ideone.com/HGGVl

最重要的是标准没有谈到应该如何填充,因此编译器可以自由做出任何决定,你不能假设所有编译器做出同样的决定.

  • @Dchris - 编译器可能会小心确保每个字段根据其自身的自然对齐进行对齐.c和d是一个字节,因此无论您将它们放在何处用于单字节CPU指令,它都会对齐.然而,int需要在4字节边界上对齐,在d之后需要两个字节的填充.这会让你达到8. (5认同)
  • 有一个`__attribute __((packed))`,它用于共享内存结构以及用于映射网络数据的结构.它不影响性能(见http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/),但它是与网络相关的结构非常有用的特性.(据我所知,这不是一个标准,所以答案仍然是正确的). (4认同)

Lin*_*cer 20

如果您有机会自己设计结构,那么它应该是可能的.基本思想是你应该设计它,这样就不需要在其中插入填充字节.第二个技巧是你必须处理结束的差异.

我将描述如何使用标量构造结构,但是您应该能够使用嵌套结构,只要您为每个包含的结构应用相同的设计.

首先,C和C++的基本事实是类型的对齐不能超过类型的大小.如果它会,那么就不可能使用分配内存malloc(N*sizeof(the_type)).

从最大的类型开始布局结构.

 struct
 {
   uint64_t alpha;
   uint32_t beta;
   uint32_t gamma;
   uint8_t  delta;
Run Code Online (Sandbox Code Playgroud)

接下来,手动填充结构,以便最终匹配最大类型:

   uint8_t  pad8[3];    // Match uint32_t
   uint32_t pad32;      // Even number of uint32_t
 }
Run Code Online (Sandbox Code Playgroud)

下一步是确定结构是否应以小端或大端格式存储.最好的办法是"交换"的所有元素在原地写入前或读取结构,如果存储格式不匹配主机系统的字节序后.

  • @Phil是一种基本类型,例如uint32_t,可以(可能)具有与其大小匹配的对齐要求,在这种情况下,为四个字节。编译器可以插入填充以实现此目的。通过手动执行此操作,编译器将无需执行此操作,因为对齐始终是正确的。缺点是,在对对齐要求不太严格的系统上,手动填充的结构将比编译器填充的结构大。您可以按升序或降序执行此操作,但是如果您按升序进行int操作,则需要在结构的中间插入更多填充。 (2认同)
  • @jwg。在一般情况下(例如,当您使用别人设计的结构时),可以插入填充以确保没有字段最终出现在硬件无法读取的位置(如其他答案所述)。但是,当您自己设计结构时,可以小心地确保不需要填充。这两个事实在任何方面都不反对!我相信这种启发式方法将适用于所有可能的体系结构(假设to的类型没有比其大小大的对齐要求,无论如何在C中都是不合法的)。 (2认同)
  • @Lindydancer - 如果您打算将它们组合成一个连续的随机内存块,而不一定只是一个同构数组,则需要填充。填充可以使您在任意边界上进行自对齐,例如 sizeof(void*) 或 SIMD 寄存器的大小。 (2认同)

Eri*_*rik 7

不,没有安全的方法.除了填充之外,还必须处理不同的字节顺序和不同大小的内置类型.

您需要定义文件格式,并将结构转换为该格式或从该格式转换.序列化库(例如boost :: serialization或google的协议缓冲区)可以帮助解决这个问题.

  • @托马斯:没错。而这只是乐趣的开始。 (2认同)