如何确定一个C ++类的大小?
简介 :编译器在编译过程中如何静态确定C ++类的大小?
详情 :
我试图理解什么规则是确定一个类将使用多less内存,以及内存将如何alignment。
例如下面的代码声明了4个类。 前两个是每个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; }
我知道答案与class级数据成员的alignment有关。 但我试图了解这些规则到底是什么以及如何在编译步骤中得到应用,因为我有一个类有一个__m128i
数据成员,但数据成员不是16字节alignment,这将导致段错误编译器使用movaps
来生成代码来访问数据。
对于POD(普通旧数据),规则通常是:
- 结构中的每个成员都有一定的尺寸和一些alignment要求a。
- 编译器将大小S设置为零,并将alignment要求A设置为一(字节)。
- 编译器按顺序处理结构中的每个成员:
- 考虑成员的alignment要求a。 如果S当前不是a的倍数,则只需添加足够多的字节S,使其成为a的倍数。 这决定了会员的去向; 它将从结构的起点偏移S(对于S的当前值)。
- 将A设置为A和a的最小公倍数。
- 将s添加到S,为该成员留出空间。
- 当为每个成员完成上述过程时,考虑结构的alignment要求A.如果S当前不是A的倍数,那么对S添加恰到好处,以便它是A的倍数。
上述结构的大小是S的值。
另外:
- 如果任何成员是一个数组,其大小就是元素数乘以每个元素的大小,其alignment要求就是元素的alignment要求。
- 如果任何成员是一个结构,其大小和alignment要求计算如上。
- 如果有任何成员是工会:
- 将S设置为最大的成员的大小。
- 将A设置为所有成员alignment的最小公倍数。
- 如果S不是A的倍数,那么只需加上S就可以使它成为A的倍数。
考虑你的TestClass3
:
- S从0开始,A从1开始。
-
char buf[8]
需要8个字节和alignment1,所以S增加8到8,而A保持1。 -
__m128i vect
需要16个字节和alignment16.首先,必须将S增加到16才能进行正确的alignment。 那么A必须增加到16,那么S就必须增加16才能腾出空间,所以S现在是32。 -
char buf2[8]
需要8个字节和alignment1,所以S增加8到24,而A保持16。 - 最后,S是24,不是A(16)的倍数,所以S必须增加8到32。
所以TestClass3
的大小是32个字节。
对于基本types( int
, double
等),alignment要求取决于实现,主要由硬件决定。 在许多处理器上,当它具有一定的alignment方式(通常是内存中的地址是其大小的倍数)时,加载和存储数据的速度会更快。 除此之外,上面的规则很大程度上都遵循逻辑; 他们把每个成员放在必须满足alignment要求的地方,而不用占用更多的空间。
这完全取决于编译器如何确定一个类的大小。 编译器通常会编译来匹配某个应用程序二进制接口,这个接口是依赖于平台的。
然而,你观察到的行为是非常典型的。 编译器正在尝试alignment这些成员,使它们每个都以其大小的倍数开始。 在TestClass3
的情况下, TestClass3
一个成员的types是__m128i
和sizeof(__m128i) == 16
。 所以它将尝试alignment该成员,以16的倍数开始。第一个成员是char[8]
types,因此占用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 │ └───────────────┴───────────────┴───────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
现在一切正确alignment – 数组的偏移量是1的倍数(它们的元素的大小), __m128i
对象的偏移量是16的倍数,总大小是32字节。
编译器本身不只是做这个重新排列的原因是因为这个标准指定了一个类的后面的成员应该有更高的地址:
具有相同访问控制(第11章)的(非联合)类的非静态数据成员被分配,以便以后的成员在类对象中拥有更高的地址。
这些规则由使用中的应用程序二进制接口规范确定,这确保了共享此接口的程序在不同系统之间的兼容性。
对于GCC,这是安腾ABI。
(不幸的是,它不再公开可用,虽然我find了镜子 。)
如果你想确保所有的alignment方式,你应该在h文件中使用“pragma pack(1)”来查看这篇文章: http : //tedlogan.com/techblog2.html