如何确定C ++类的大小?

时间:2013-01-24 21:03:32

标签: c++ memory-alignment

摘要:编译期间编译器如何静态确定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生成代码来访问数据时的段错误。

4 个答案:

答案 0 :(得分:13)

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

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

当完成上述操作时,结构的大小是S的值。

此外:

  • 如果任何成员是数组,则其大小是元素的数量乘以每个元素的大小,其对齐要求是元素的对齐要求。
  • 如果任何成员是结构,其大小和对齐要求按上述计算。
  • 如果任何成员是工会:
    1. 将S设置为最大成员的大小。
    2. 将A设置为所有成员对齐的最小公倍数。
    3. 如果S不是A的倍数,则添加足够的S以使其成为A的倍数。

考虑您的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 :(得分:8)

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

然而,您观察到的行为非常典型。编译器正在尝试对齐成员,以便它们各自以其大小的倍数开始。对于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条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。

答案 2 :(得分:0)

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

对于GCC,这是Itanium ABI。

(不幸的是,它不再公开,但我找到了a mirror。)

答案 3 :(得分:-1)

如果要确保对齐,则应使用h文件中的“pragma pack(1)” 看看这篇文章: http://tedlogan.com/techblog2.html