数据对齐
结构体数据对齐:指结构体内各个数据的内存地址的对齐
在结构体中的第一个成员的首地址等于整个结构体的变量的首地址
后面的成员地址随着它声明的首地址和实际占用的字节数递增。
而为了总的结构体大小对齐,会在结构体中插入一些没有实际意义的字符填充结构体
通俗点讲,计算机系统对基本类型的数据在内存中存放的位置有限制,系统会要求这些数据的首地址的值是某个数(这个数一般为4或者8的)的倍数,这就是所谓的内存对齐
而32位机器上默认的对齐模数一般为4,64位机上位8。
在结构体中,成员数据对齐满足以下规则:
- 结构体重的第一个成员的首地址即时结构体变量的首地址。
- 结构体中的每一个成员的首地址相对于结构体IDE首地址的偏移量是该成员数据类型大小的整数倍。
- 结构体的总大小是对齐模数(对齐模数等于#pragma pack(n)所指定的n与结构体重最大数据类型的成员大小的最小值)的整数倍
Example:
1 | struct One{ |
struct | type | pack(4) | pack(8) |
---|---|---|---|
one | double | 8 | 8 |
char | 1+3 | 1+3 | |
int | 4 | 4 | |
result | 16 | 16 | |
two | |||
char | 1+3 | 1+7 | |
double | 8 | 8 | |
int | 4 | 4+4 | |
result | 16 | 24 |
进阶C++
C++中的数据对齐
环境:macOS 11.13.6 64位
编译器:clang-902.0.39.2
系统int大小为4字节,指针大小为8字节。
空类
1 | class A {}; |
空类sizeof的结果为1,为什么不是0呢?因为C++标准规定两个不同实例的内存地址必须不同,所以用这一个字节来占用不同的内存地址,让空类的两个实例可以相互区分。
单个数据类型
大多数编译器支持空基类优化(Empty Base Class Optimization, EBCO),即从空基类中派生出来的类并不会增加1字节,如:
1 | class B:public A{ |
sizeof(B)的结果为4而不是5或8。
静态数据成员类型
1 | class C{ |
sizeof 结果为4,静态数据成员被存放在类对象之外。
带非虚函数成员的类
1 | class D { |
sizeof(D)结果为1,无论是普通成员函数还是静态成员函数都被存放在类对象之外。
带虚函数成员的类
1 | class E { |
sizeof(E)结果为8,带虚函数成员的类对象会包含一个指向该类的virtual table的指针。
普通派生类
1 | class G : public C { |
sizeof(G)的结果为8,派生类会存放基类中非静态数据成员(C中的a)的副本。
基类带虚函数的派生类
1 | class H : public E {}; |
sizeof(H)结果为8,由于基类中带虚函数,派生类中也必须保存一个指向派生类的virtual table的指针。
多重继承的派生类
1 | class E1 { |
sizeof(E3)结果为16,子类中保存了两个virtual table的指针
虚继承的派生类
1 | class H1{}; |
sizeof(H3)的结果为16,是两个基类中的virtual table指针
普通类的对齐规则
1 | class F { |
sizeof(F)的结果为8而不是5,由于F的最大对齐值为4(int),因此a和b之间被补齐3字节。
多重继承下的对齐规则
1 | class G1{ |
sizeof(G3)的结果为48,默认对齐模数为8的情况下
G1 = (8)+(4)+(8)+(8)
G2 = (4+4)+(1+7)+(8)
G3 = 48