【问题标题】:Determining the alignment of C/C++ structures in relation to its members确定 C/C++ 结构相对于其成员的对齐方式
【发布时间】:2010-09-26 17:47:51
【问题描述】:

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

例如。为:

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 合格)无效。
      • 这意味着我提出的使用 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++ non-POD 移植
  • 编译器内在函数,例如。 MSVC __alignof
    • 适用于灵活的数组
    • alignof 关键字在最新的 c++ 草案中

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

正如我所见,只有 1 个解决方案 - 使用旧的 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 没有此属性

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

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

【问题讨论】:

  • 底线是只有编译器知道它在编译时将如何对齐对象,因此获取此信息的唯一方法是从编译器。在标准化之前,您将需要使用编译器扩展。你在做什么需要这些信息?
  • 虚拟机(对象、数组、垃圾收集器和许多其他杂项所需的低级构造)。这是一个学生项目,应该尽可能“便携”和“符合标准”。
  • 让它工作不是问题(而且大部分已经完成),但我没有时间在多个平台和编译器上测试项目,所以我正在寻找一些符合标准的解决方案我面临的问题。
  • 我确实意识到这在标准方面很难做到。我已经总结了这些信息,希望它对寻找相同事物的其他人有所帮助。 (cmets 300 个字符的限制是荒谬的)
  • 至于标准,我也很失望,因为在最新的 C 草案中几乎没有提到对齐 - 几年后,c++ 可能会变得比 C 更“低级”,因为它的alignof 和对齐关键字 :)

标签: c++ c alignment


【解决方案1】:

我认为任何 C 标准都不能保证内存布局。这在很大程度上取决于供应商和架构师。可能有一些方法在 90% 的情况下都有效,但它们不是标准的。

我很高兴被证明是错误的,不过 =)

【讨论】:

  • 标准实际上做了几个保证,包括:1)结构的第一个成员之前没有填充,2)结构成员按照定义的顺序排列在内存中,3 ) 数组在其成员之间没有填充。
  • 将这些保证与所有结构都可以用作数组元素的事实相结合,因此结构的对齐必须至少与其最严格的成员的对齐一样严格。
  • 我知道我们在这里讨论的是 C,但为了清楚起见,C++ 放宽了在某些条件下按定义顺序排列结构成员的要求。
  • @Mike B True,但如果类型是 POD,C 规则仍然适用。
【解决方案2】:

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

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

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

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

如果结构的对齐方式至少不如它的每个成员那么严格,您将无法创建结构数组,因为某些结构成员的某些元素不会正确对齐。

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

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

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

【讨论】:

  • 我相信你是对的。但我发现很难从这三个事实中得出合乎逻辑的结论。你能概述一下主要步骤吗?
  • 等等,我想我明白了。如果结构的对齐不那么严格,那么就没有办法正确填充成员。 struct 的对齐方式必须是其所有成员对齐方式的倍数,对吗?
  • 我不确定你没有遵循什么,我想我可以说的差不多了。你接受我的三个陈述吗?如果是这样,一个结构没有与其最严格成员对齐的结构将在此类结构的数组中具有未正确对齐的成员。
  • 我想你现在已经明白了,有一种方法你认为我可以更清楚,请告诉我,我会更新我的答案。
  • 我认为你的回答很好。现在明白了:)
【解决方案3】:

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

旁注:我知道这个问题是关于对齐的,但另一个问题是填充。对于嵌入式编程、二进制数据等等——一般来说,如果可能的话,不要假设任何关于结构对齐的事情。如有必要,请在结构中使用显式填充。我遇到过这样的情况,如果不添加填充元素,就不可能将一个编译器中使用的精确对齐复制到不同平台上的编译器。它与结构内部结构的对齐有关,因此添加填充元素修复了它。

【讨论】:

  • 我认为您将对齐与填充混淆了,它们是相关但不同的概念。填充用于确保正确对齐,这是 pack pragma 处理的;如果您需要在结构中使用显式填充,则几乎可以肯定访问它的代码有问题。
  • 如果您对嵌入式设备或二进制通信进行编程,您最终会经常处理对齐填充。虽然问题是关于对齐的,但我只是想提一下填充,以防这是一个附带问题。
  • 我也很好奇您的陈述,“...如果您需要在您的结构中使用显式填充...”您是否编写过嵌入式设备和跨平台/编译器?你见过 Linux 内核代码吗?有时这是绝对必要的。
  • @Robert Gamble:“几乎可以肯定代码有问题”不一定。如果要确保两个元素不会出现在同一 Cache Line 上,为了防止在多核处理器上产生 ping-pong,您需要手动在元素之间添加正确的填充量。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

    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;
    

    【讨论】:

    • 这应该等同于 std::tr1::alignment_of::value,尽管它是一个非常干净和简单的实现。
    • 不知道 std::tr1::alignment_of... 太糟糕了,我也进行了一些搜索,但没有找到任何东西。感谢您提及。
    【解决方案6】:

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

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

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


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

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

    【讨论】:

    • 这将为您提供编译器选择用于给定结构中的类型 t 的对齐方式,但这可能不是类型 t 未出现在此类结构中时的对齐方式。
    • 我承认标准可能无法保证此宏将起作用,因此使用#if 预处理器控件保护它可能是合适的,以便为特定编译器正确定义它。也就是说,它似乎在实践中运行良好。
    • 另外,我记得,offsetof 只保证只适用于 POD,所以如果 t 是非 POD,这又超出了标准。
    【解决方案7】:

    如果您想针对 Windows 中的特定情况找出此问题,请打开 windbg:

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

    然后,运行:

    dt somemodule!CSomeType
    

    【讨论】:

      【解决方案8】:

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

      这意味着如果你有一个带有浮点数、长双精度数、uchar 等的 typedef... 包括各种数组。然后有另一种类型,其中包含一些奇怪形状的成员,一个字节,然后是另一个奇怪的成员,它将简单地按照 make/solution 文件定义的任何偏好对齐。

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

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

      【讨论】:

        【解决方案9】:

        修改自Peeter Joot's Blog

        C 结构对齐基于结构中最大尺寸的本机类型,至少通常是这样(例外情况是在 win32 上使用 64 位整数,只需要 32 位对齐)。

        如果您只有字符和字符数组,则添加一个 int 后,该 int 将最终从 4 字节边界开始(在 int 成员之前可能有隐藏的填充)。此外,如果结构不是 sizeof(int) 的倍数,则将在末尾添加隐藏填充。 short 和 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

        【讨论】:

          【解决方案10】:

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

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

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

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

          【讨论】:

          • 我确实会奇怪并且不符合标准,因为对齐值始终是 2 的幂:6.2.8 对象对齐 [...] 4 [...] 每个有效的对齐值都应该是 2 的非负整数幂。
          • 感谢您指出标准不允许对齐值是 2 的幂以外的任何值。这是在 C11 中添加还是在 C99 中也存在?我在某些平台上看到过 80 位浮点数,当然它的对齐要求可能仍然是 2 的幂。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-05-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-21
          • 2013-07-24
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多