【问题标题】:Is it in C++ standard to align such structures对齐这些结构是否符合 C++ 标准
【发布时间】:2012-10-28 17:36:19
【问题描述】:

举个例子

class A
{
public:
  int a; 
  char b;
  int c;
};

我看到的每个编译器(对于 x86、32 或 64 位)都为类 A 分配 12 个字节,而不是 9 个。因此它们将 b 与您可以说的整数边界或总线边界对齐。我的问题是这是否符合 C++ 标准,是否有任何编译器不这样做。

【问题讨论】:

    标签: c++ x86


    【解决方案1】:

    在您的情况下,“b”始终正确对齐。 padded 以在 32 位边界中对齐 c。尽管有一些特定实现的空间,但大多数编译器都遵循将 2 字节和 4 字节变量与 2 字节和 4 字节边界对齐的规则。

    在 32 位系统中,double 和 long int(8 字节)也对齐到 4 字节边界,但在 64 位系统中对齐到 8 字节。

    【讨论】:

    • 系统如何对齐不同的类型非常依赖于实现。我使用了 32 位系统,其中双精度在 8 字节边界上对齐,并且我已经看到 long double 的各种对齐方式。
    • 嗯,是的,也许 long double 正在推动兼容性的极限。
    【解决方案2】:

    C++ 标准规定:

    • 对象具有大小为倍数的对齐要求(因此,如果 int 的宽度为 4 个字节,则它需要 1、2 或 4 个字节的对齐,具体取决于实现)。
    • 成员对象(如果它们没有被public等访问说明符分隔)全部按照它们声明的顺序分配
    • 并且按照对齐要求进行分配。

    所以不,标准并没有明确说明该类的大小应为 12 字节。

    但它确实b应该在a之后分配,而c应该在b之后分配。

    int 为 4 字节宽且需要 4 字节对齐的平台上,这会留下 12 字节作为最小有效大小:

    • a 占用前 4 个字节
    • b 占用一个字节
    • c 需要 4 个字节,必须在 4 个字节的边界上分配。 b 结束了一个字节过去这样的边界,因此通过插入 3 个字节的填充找到放置 c 的下一个有效位置。

    所以类的总大小最终是成员的大小 (4 + 1 + 4 = 9) 加上三个字节的填充,总共 12 个。

    这里还有一条无效的规则,但是如果您按照a, c, b 的顺序定义了成员,这将很重要。

    包含类 (A) 从最严格对齐的成员对象继承对齐要求。也就是说,因为它包含一个int,它具有与int 相同的对齐要求。并且因为对象的总大小必须是其对齐要求的倍数,包含顺序为a, b, c 的成员的类仍然需要 12 个字节的存储空间。它只是将 3 个字节的填充移到类的末尾,而不是在 bc 之间。

    但是,在某些其他情况下,按大小降序重新排序成员有时会减小类的大小。

    假设我们有一个这样的类:

    class B {
      char a;
      double b;
      int c;
    };
    

    这将需要 24 个字节的存储空间(a 需要 1 个字节,b 需要 8 个字节,c 需要 4 个字节,但是为了确保b 以 8 字节边界结束,我们需要在ab 之间有 7 个字节的填充,并且为了确保整个类的大小是 8 的倍数,我们需要在 @ 之后再增加 4 个字节987654347@.

    但是根据大小重新排序成员,如下所示:

    class B {
      double b;
      int c;
      char a;
    };
    

    生成一个只需要 16 个字节的类:

    成员对象本身也是相同的 1 + 4 + 8 字节,但现在 c 已经在 4 字节边界上对齐(因为它位于以 8 字节边界结束的 b 之后),并且a 从来不需要任何对齐,所以我们唯一需要的对齐是确保B 的大小是8 的倍数。成员占用13 个字节,所以我们可以添加3 个字节的填充,类结束最多 16 个字节,比第一个版本小 33%。

    【讨论】:

    • 您可能还指出,在大多数架构上,访问未对齐的对象会导致某种总线错误,从而导致程序崩溃。在 Intel 上,程序不会崩溃,但如果数据未对齐,它的运行速度仍然会慢很多。
    【解决方案3】:

    可以在编译时使用 pragma 指令控制结构打包。 See #Pragma Pack

    例如,以下不对齐成员,而是将它们彼此相邻放置。

    #pragma pack(push, 1)
    struct A4
    {
      char a;
      double b;
      char c;
    };
    #pragma pack(pop)
    

    来自 here 的示例。

    GCC 也支持编译指示包。该指令不是某些标准的一部分,但很多编译器都支持它。


    但是,不应该有这样做的理由。编译器将它们对齐以加快对成员的访问,并且应该没有理由改变它

    【讨论】:

    • 但是在 C++ 标准中是否定义了默认情况是与总线边界对齐。
    • GCC 支持一组#pragma 指令,但它们支持这一点,因为 MS 的编译器也支持。 pack 指令不是任何标准 AFAIK 的一部分。
    • 事实上,#pragma anything 根据定义不属于任何标准。
    • @user1018562 在 C++ 中定义了一个实现可以在任何它想要的地方添加填充,除了在结构的开头。 (当然,在实践中,除非认为有必要,否则任何实现都不会添加填充,以避免程序崩溃,或者在英特尔的情况下,避免不必要地减慢进程。)
    • “不应该有这样做的理由” 有这样的用例,例如packing structures for network transport
    【解决方案4】:

    是的,标准中到处都提到了对齐,主要是第 3.11 节(对齐)。它依赖于平台,因此任何依赖于对象实际大小的程序本质上都是不可移植的。

    【讨论】:

      【解决方案5】:

      标准允许编译器在必要时填充更多字节。它基本上取决于主机的架构而不是编译器。

      【讨论】:

        猜你喜欢
        • 2017-10-24
        • 1970-01-01
        • 2011-09-08
        • 2013-12-17
        • 2011-10-31
        • 1970-01-01
        • 2011-11-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多