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

时间:2008-12-12 23:24:36

标签: c++ c alignment

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

EG。为:

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

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

在互联网上搜索我发现“对于结构化类型,其任何元素的最大对齐要求决定了结构的对齐”(在What Every Programmer Should Know About Memory中)但我在标准中找不到任何类似的东西(更准确的最新草稿)。


编辑: 非常感谢所有的答案,特别是Robert Gamble,他对原始问题以及其他贡献者提供了非常好的答案。

简而言之:

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

至于确定结构的排列方式,我们提出了一些选项,并且通过一些研究,我发现了这一点:

  • 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提供了一个简单的模板解决方案,用于计算答案中的对齐方式
    • 这似乎非常接近于提升正在进行的操作(如果它小于计算的对齐方式,则会另外将对象大小作为对齐返回,因此可能会应用相同的通知
    • 这适用于灵活数组
  • 使用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<BigEnoughNumber>>::value应该给我们最大的对齐
      • 仅草稿,尚未标准(如果有),tr1::aligned_storage没有此属性

对此的任何想法也将受到赞赏。

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

10 个答案:

答案 0 :(得分:26)

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

  1. 处理器访问特定对象所需的对齐
  2. 编译器实际用于将对象放入内存的对齐
  3. 为了确保结构构件的对齐要求,结构的对齐必须至少与其最严格的构件的对齐一样严格。我不认为标准中明确说明了这一点,但可以从以下事实中推断出来(在标准中单独列出):

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

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

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

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

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

答案 1 :(得分:13)

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

template <class T>
class Traits
{
public:
    struct AlignmentFinder
    {
        char a; 
        T b;
    };

    enum {AlignmentOf = sizeof(AlignmentFinder) - sizeof(T)};
};

现在你可以去:

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

答案 2 :(得分:6)

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

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

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


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

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

答案 3 :(得分:3)

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

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

答案 4 :(得分:3)

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

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

答案 5 :(得分:2)

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

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

然后,运行:

dt somemodule!CSomeType

答案 6 :(得分:1)

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

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

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

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

答案 7 :(得分:1)

Peeter Joot's Blog修改

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 :(得分:1)

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

我很高兴被证明是错的,但是=)

答案 9 :(得分:0)

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

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

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

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