确定与其成员相关的C / C ++结构的对齐方式

如果已知结构构件的对齐,是否可以找到结构类型的对齐?

例如。 对于:

struct S { a_t a; b_t b; c_t c[]; }; 

是S = max的对齐(alignment_of(a),alignment_of(b),alignment_of(c))?

在互联网上搜索我发现“对于结构化类型,其任何元素的最大对齐要求决定了结构的对齐”(在每个程序员应该知道的内存中 )但我在标准中找不到任何类似的东西(最新)草稿更准确)。


编辑:非常感谢所有答案,特别是罗伯特·甘博,他对原始问题和其他贡献者提供了非常好的答案。

简而言之:

为了确保结构构件的对准要求,结构的对准必须至少与其最严格构件的对准一样严格。

至于确定结构的对齐方式,我们提出了一些选项,经过一些研究,我发现了这个:

  • c ++ std :: tr1 :: alignment_of
    • 尚未标准,但关闭(技术报告1),应该在C ++ 0x中
    • 最新草案中存在以下限制:前提条件:T应为完整类型,引用类型或未知范围的数组,但不应为函数类型或(可能是cv-qualified)void。
      • 这意味着我使用C99灵活数组的用例不起作用(这并不奇怪,因为灵活的数组不是标准的c ++)
    • 在最新的c ++草案中,它是用新关键字的术语定义的 – alignas(这具有相同的完整类型要求)
    • 在我看来,如果c ++标准曾经支持C99灵活数组,那么要求可以放宽(结构与灵活数组的对齐不应该根据数组元素的数量而改变)
  • c ++ boost :: alignment_of
    • 主要是tr1替代品
    • 似乎是专门针对void而在这种情况下返回0(这在c ++草案中是禁止的)
    • 开发人员注意:严格来说,你应该只依赖于ALIGNOF(T)的值是T的真正对齐的倍数,尽管在实践中它确实在我们所知道的所有情况下计算了正确的值。
    • 我不知道这是否适用于灵活的数组,它应该(可能不能正常工作,这解析为我平台上的编译器内在因此我不知道它在一般情况下会如何表现)
  • Andrew Top提供了一个简单的模板解决方案,用于计算答案中的对齐方式
    • 这似乎与boost正在做的非常接近(如果它小于计算的对齐,则boost会另外返回对象大小,因为我可以看到相同的通知)
    • 这适用于灵活的数组
  • 使用Windbg.exe找出符号的对齐方式
    • 没有编译时间,编译具体,没有测试它
  • 在包含该类型的匿名结构上使用offsetof
    • 看到答案,不可靠,不能用c ++非POD移植
  • 编译器内在函数,例如。 MSVC __alignof
    • 适用于灵活的数组
    • alignof关键字在最新的c ++草案中

如果我们想要使用“标准”解决方案,我们只限于std :: tr1 :: alignment_of,但如果你将c ++代码与c99的灵活数组混合,这将无法工作。

我认为它只有一个解决方案 – 使用旧的struct hack:

 struct S { a_t a; b_t b; c_t c[1]; // "has" more than 1 member, strictly speaking this is undefined behavior in both c and c++ when used this way }; 

在这种情况下(以及其他所有情况),不同的c和c ++标准及其不断增长的差异是不幸的。


另一个有趣的问题是(如果我们无法以便携方式找出结构的对齐方式)可能的最严格的对齐要求是什么。 我可以找到几种解决方案:

  • boost(内部)使用各种类型的并集,并在其上使用boost :: alignment_of
  • 最新的c ++草案包含std :: aligned_storage
    • 对于任何大小不大于Len的C ++对象类型,default-alignment的值应该是最严格的对齐要求
      • 所以std::alignment_of< std::aligned_storage>::value应该给我们最大的对齐
      • 仅草稿,尚未标准(如果有), tr1::aligned_storage没有此属性

对此的任何想法也将不胜感激。

我暂时取消选中已接受的答案,以获得更多关于新子问题的可见性和输入

这里有两个密切相关的概念:

  1. 处理器访问特定对象所需的对齐方式
  2. 编译器实际用于将对象放入内存的对齐方式

为了确保结构构件的对准要求,结构的对准必须至少与其最严格构件的对准一样严格。 我不认为这在标准中明确说明,但可以从以下事实中推断出来(在标准中单独列出):

  • 结构允许在其成员之间(以及最后)填充
  • 不允许数组在其元素之间填充
  • 您可以创建任何结构类型的数组

如果结构的对齐方式至少与其每个成员一样严格,则无法创建结构数组,因为某些结构成员的某些元素将无法正确对齐。

现在,编译器必须根据其成员的对齐要求确保结构的最小对齐,但它也可以以比所需更严格的方式对齐对象,这通常是出于性能原因而进行的。 例如,许多现代处理器将允许以任何对齐方式访问32位整数,但如果它们未在4字节边界上对齐,则访问可能会明显变慢。

没有可移植的方法来确定处理器对任何给定类型强制执行的对齐,因为语言没有公开这种对齐,尽管由于编译器显然知道目标处理器的对齐要求,因此它可以将此信息公开为扩展。

虽然许多编译器可以选择对对齐提供某种程度的控制,但也没有可移植的方式(至少在C中)来确定编译器如何实际对齐对象。

我编写了这种类型的特征代码来确定任何类型的对齐方式(基于已经讨论过的编译器规则)。 您可能会发现它很有用:

 template  class Traits { public: struct AlignmentFinder { char a; T b; }; enum {AlignmentOf = sizeof(AlignmentFinder) - sizeof(T)}; }; 

所以现在你可以去:

 std::cout << "The alignment of structure S is: " << Traits::AlignmentOf << std::endl; 

以下宏将返回任何给定类型的对齐要求(即使它是结构):

 #define TYPE_ALIGNMENT( t ) offsetof( struct { char x; t test; }, test ) 

注意:我可能在过去的某些时候从微软的标题中借用了这个想法……


编辑:正如Robert Gamble在评论中指出的那样,这个宏并不能保证有效。 实际上,如果将编译器设置为在结构中打包元素,它肯定不会很好地工作。 因此,如果您决定使用它,请谨慎使用。

某些编译器有一个扩展,允许您获取类型的对齐方式(例如,从VS2002开始,MSVC具有__alignof()内在函数)。 那些应该在可用时使用。

正如其他人所提到的,它的实现依赖。 Visual Studio 2005使用8个字节作为默认结构对齐方式。 在内部,项目按其大小对齐 – 浮点数具有4字节对齐,双精度数字使用8,等等。

您可以使用#pragma pack覆盖该行为。 GCC(和大多数编译器)具有类似的编译器选项或编译指示。

如果您了解有关正在使用的编译器选项的更多详细信息,则可以假设结构对齐。 例如,#pragma pack(1)将强制某些编译器在字节级别上进行对齐。

旁注:我知道问题是关于对齐,但边缘问题是填充。 对于嵌入式编程,二进制数据等 – 通常,如果可能,不要假设任何有关结构对齐的内容。 而是在结构中根据需要使用显式填充。 我有过这样的情况:如果不添加填充元素,就不可能将一个编译器中使用的精确对齐复制到不同平台上的编译器。 它与结构内部结构的对齐有关,因此添加填充元素可以修复它。

如果你想在Windows中查找特定情况,请打开windbg:

 Windbg.exe -z \path\to\somemodule.dll -y \path\to\symbols 

然后,运行:

 dt somemodule!CSomeType 

我认为在任何C标准中都不会以任何方式保证内存布局。 这是非常依赖供应商和架构师的。 可能有一些方法可以在90%的情况下工作,但它们不是标准的。

虽然=)我会很高兴被certificate是错的

我主要同意Paul Betts,Ryan和Dan。 真的,这取决于开发人员,你可以保留罗伯特注意到的默认对齐symanic(罗伯特的解释只是默认行为而不是强制或要求的任何方式),或者你可以设置你想要的任何对齐/ Zp [# #]。

这意味着如果你有一个带浮动的typedef,long double,uchar等等……包括各种各样的数组。 然后有另一种类型,其中包含一些这些形状奇特的成员,以及一个字节,然后是另一个奇数成员,它将简单地按照make / solution文件定义的任何偏好进行对齐。

如前所述,在运行时使用windbg的dt命令可以了解编译器如何在内存中布局结构。

您还可以使用任何pdb读取工具(如dia2dump)从pdb静态提取此信息。

修改自Peeter Joot的博客

C结构对齐基于结构中最大的本机类型,至少一般(例如,在win32上使用64位整数,只需要32位对齐)。

如果你只有字符和字符数组,一旦你添加了一个int,那个int最终将从4字节边界开始(在int成员之前可能有隐藏的填充)。 此外,如果结构不是sizeof(int)的倍数,则会在末尾添加隐藏填充。 对于简短和64位类型也是如此。

例:

 struct blah1 { char x ; char y[2] ; }; 

sizeof(blah1)== 3

 struct blah1plusShort { char x ; char y[2] ; // <<< hidden one byte inserted by the compiler here // <<< z will start on a 2 byte boundary (if beginning of struct is aligned). short z ; char w ; // <<< hidden one byte tail pad inserted by the compiler. // <<< the total struct size is a multiple of the biggest element. // <<< This ensures alignment if used in an array. }; 

sizeof(blah1plusShort)== 8

8年后我读到了这个答案,我觉得@Robert接受的答案一般都是正确的,但在数学上是错误的。

为了确保结构构件的对准要求,结构的对准必须至少与其构件的对准的最小公倍数一样严格。 考虑一个奇怪的例子,成员的对齐要求是4和10; 在这种情况下,结构的对齐是LCM(4,10),它是20,而不是10.当然,看到具有这种对齐要求的平台,这不是2的幂,这是奇怪的,因此对于所有实际情况,结构对齐等于其成员的最大对齐。

这样做的原因是,只有当结构的地址以其成员对齐的LCM开始时,才能满足所有成员的对齐,并且成员和结构之间的填充与起始地址无关。 。

更新:正如@chqrlie在评论中指出的那样,C标准不允许对齐的奇数值。 然而,这个答案仍然certificate了为什么结构对齐是其成员对齐的最大值,因为最大值恰好是最小公倍数,因此成员总是相对于公共多个地址对齐。