27

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

例如。为了:

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中)但我在标准中找不到任何类似的东西(最新更准确地起草)。


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

简而言之:

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

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

  • c++ std::tr1::alignment_of
    • 尚未标准,但接近(技术报告 1),应该在 C++0x 中
    • 最新草案中存在以下限制: 前提条件:T 应是完整类型、引用类型或未知边界数组,但不应是函数类型或(可能是 cv 限定的)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++ 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没有此属性

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

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

4

10 回答 10

28

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

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

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

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

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

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

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

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

于 2008-12-12T23:29:53.030 回答
14

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

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;
于 2008-12-13T00:29:56.200 回答
7

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

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

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


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

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

于 2008-12-13T01:44:14.397 回答
3

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

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

于 2008-12-12T23:31:37.503 回答
3

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

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

于 2008-12-12T23:36:02.657 回答
2

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

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

然后,运行:

dt somemodule!CSomeType
于 2008-12-13T01:57:44.557 回答
1

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

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

于 2008-12-12T23:28:29.087 回答
1

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

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

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

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

于 2009-02-21T02:54:17.790 回答
1

改编自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

于 2012-10-20T17:55:38.023 回答
1

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

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

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

Update: As pointed out by @chqrlie in the comment, C standard does not allow the odd values of the alignment. However this answer still proves why structure alignment is the maximum of its member alignments, just because the maximum happens to be the least common multiple, and thus the members are always aligned relative to the common multiple address.

于 2017-01-18T10:36:31.070 回答