【问题标题】:How is the size of a C++ class determined?C++ 类的大小是如何确定的?
【发布时间】:2013-01-24 21:03:32
【问题描述】:

总结:编译器如何在编译过程中静态确定C++类的大小?

详情

我试图了解用于确定类将使用多少内存以及如何对齐内存的规则。

例如,以下代码声明了 4 个类。前 2 个各 16 个字节。但是 3 是 48 字节,尽管它包含与前 2 相同的数据成员。而第四类与第三类具有相同的数据成员,只是顺序不同,但它是 32 字节。

#include <xmmintrin.h>
#include <stdio.h>

class TestClass1 {
  __m128i vect;
};

class TestClass2 {
  char buf[8];
  char buf2[8];
};

class TestClass3 {
  char buf[8];
  __m128i vect;
  char buf2[8];
};

class TestClass4 {
  char buf[8];
  char buf2[8];
  __m128i vect;
};


TestClass1 *ptr1;
TestClass2 *ptr2;
TestClass3 *ptr3;
TestClass4 *ptr4;
int main() {
  ptr1 = new TestClass1();
  ptr2 = new TestClass2();
  ptr3 = new TestClass3();
  ptr4 = new TestClass4();
  printf("sizeof TestClass1 is: %lu\t TestClass2 is: %lu\t TestClass3 is: %lu\t TestClass4 is: %lu\n", sizeof(*ptr1), sizeof(*ptr2), sizeof(*ptr3), sizeof(*ptr4));
  return 0;
}

我知道答案与类的数据成员的对齐有关。但是我试图准确地理解这些规则是什么以及它们在编译步骤中是如何应用的,因为我有一个具有__m128i 数据成员的类,但该数据成员不是 16 字节对齐的,这会导致段错误当编译器使用movaps 生成代码来访问数据时。

【问题讨论】:

  • 规则完全是实现定义的,你应该检查你的编译器的文档。此外,几乎所有编译器都允许您使用 #pragma 或属性为类成员指定所需的对齐方式。
  • “数据成员未对齐”:然后您在指针/引用转换、自定义分配或类似方面做了一些不愉快的事情。

标签: c++ memory-alignment


【解决方案1】:

对于 POD(普通旧数据),规则通常是:

  • 结构中的每个成员都有一些大小s和一些对齐要求a
  • 编译器开始时将大小 S 设置为零,并将对齐要求 A 设置为一(字节)。
  • 编译器按顺序处理结构中的每个成员:
  1. 考虑成员的s对齐要求a。如果 S 当前不是 a 的倍数,则添加足够的字节 S 使其成为 a 的倍数时间>。这决定了会员的去向;它将在结构开头的偏移量 S 处(对于 S 的当前值)。
  2. A 设置为 Aa 的最小公倍数1
  3. s 添加到 S,为成员留出空间。
  • 为每个成员完成上述过程后,考虑结构的s对齐要求A。如果 S 当前不是 A 的倍数,则将足够的内容添加到 S 使其成为 A 的倍数em>。

结构体的大小就是上面做完后S的值。

另外:

  • 如果任何成员是数组,则其大小为元素数乘以每个元素的大小,其对齐要求为元素的对齐要求。
  • 如果任何成员是结构,则其大小和对齐要求按上述计算。
  • 如果任何成员是联合体,则其大小为其最大成员的大小加上刚好足以使其成为所有成员对齐的最小公倍数1的倍数。李>

考虑你的TestClass3

  • S 从 0 开始,A 从 1 开始。
  • char buf[8] 需要 8 个字节和对齐 1,所以 S 增加 8 到 8,而 A 保持 1。
  • __m128i vect 需要 16 个字节和 16 个对齐。首先,必须将 S 增加到 16 以提供正确的对齐。然后 A 必须增加到 16。然后 S 必须增加 16 为 vect 腾出空间,所以 S 现在是 32。
  • char buf2[8] 需要 8 个字节和对齐 1,因此 S 增加 8 到 24,而 A 仍然是 16。
  • 最后S是24,不是A(16)的倍数,所以S必须加8到 32 岁。

所以TestClass3的大小是32字节。

对于基本类型(intdouble 等),对齐要求是实现定义的,通常主要由硬件决定。在许多处理器上,当数据具有一定的对齐方式时(通常当其在内存中的地址是其大小的倍数时)加载和存储数据会更快。除此之外,上述规则主要来自逻辑。他们将每个成员放在必须满足对齐要求的位置,而不会占用不必要的空间。

脚注

1 对于一般情况,我将其表述为使用对齐要求的最小公倍数。但是,由于对齐要求始终是 2 的幂,因此任何一组对齐要求的最小公倍数都是其中最大的。

【讨论】:

  • 另请注意,通常但不总是,将成员从最大到最小重新排列将最大限度地减少浪费的填充空间。
  • 需要注意的是,这不是C++指定的。它由平台/硬件的 C++ ABI 指定。
【解决方案2】:

如何确定类的大小完全取决于编译器。编译器通常会编译以匹配特定的应用程序二进制接口,这取决于平台。

但是,您观察到的行为非常典型。编译器正在尝试对齐成员,以便它们每个都以它们大小的倍数开始。在TestClass3 的情况下,成员之一的类型为__m128isizeof(__m128i) == 16。因此它会尝试将该成员对齐以从 16 的倍数的字节开始。第一个成员的类型为 char[8],因此占用 8 个字节。如果编译器将 _m128i 对象直接放在第一个成员之后,它将从位置 8 开始,这不是 16 的倍数:

0               8               16              24              32              48
┌───────────────┬───────────────────────────────┬───────────────┬┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│    char[8]    │            __m128i            │    char[8]    │           
└───────────────┴───────────────────────────────┴───────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄

所以它更喜欢这样做:

0               8               16              24              32              48
┌───────────────┬┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┬───────────────────────────────┬───────────────┐┄┄┄
│    char[8]    │               │           __m128i             │    char[8]    │
└───────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┴───────────────────────────────┴───────────────┘┄┄┄

这使它的大小为 48 字节。

当您对成员重新排序以获得TestClass4 时,布局变为:

0               8               16              24              32              48
┌───────────────┬───────────────┬───────────────────────────────┬┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│    char[8]    │    char[8]    │           __m128i             │        
└───────────────┴───────────────┴───────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄

现在一切都正确对齐了 - 数组的偏移量是 1 的倍数(其元素的大小),__m128i 对象的偏移量是 16 的倍数 - 总大小为 32 字节.

编译器不只是自己做这种重新排列的原因是因为标准规定类的后面的成员应该有更高的地址:

分配具有相同访问控制(第 11 条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。

【讨论】:

    【解决方案3】:

    这些规则是由正在使用的应用程序二进制接口规范确定的,它确保了不同系统之间共享此接口的程序的兼容性。

    对于 GCC,这是 Itanium ABI。

    (不幸的是,它不再公开,尽管我确实找到了a mirror。)

    【讨论】:

    • @EricPostpischil: gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html The GNU C++ compiler uses an industry-standard C++ ABI starting with version 3. Details can be found in the ABI specification. 专门链接到代码源页面
    • 我想我应该讨论 ABI 的 Code Sourcery 集,而不是他们的 Itanium 一个
    【解决方案4】:

    如果你想确保对齐,你应该在你的 h 文件中使用“pragma pack(1)” 看看这个帖子: http://tedlogan.com/techblog2.html

    【讨论】:

    • 这可以防止填充,这实际上意味着很可能有几个数据成员会错位。
    • 那将是 #pragma pack(2) - 对于大多数架构上大于 16 位的东西仍然不是最佳选择(例如,在 x86 上,您访问常规 ints 的速度会变慢,在其他架构甚至硬件例外)。
    猜你喜欢
    • 2014-12-19
    • 1970-01-01
    • 2018-12-01
    • 2010-10-30
    • 2012-03-18
    • 2010-09-18
    • 2020-09-24
    • 1970-01-01
    • 2010-09-05
    相关资源
    最近更新 更多