结构体数据对齐详解

数据对齐

结构体数据对齐:指结构体内各个数据的内存地址的对齐
在结构体中的第一个成员的首地址等于整个结构体的变量的首地址
后面的成员地址随着它声明的首地址和实际占用的字节数递增。
而为了总的结构体大小对齐,会在结构体中插入一些没有实际意义的字符填充结构体

通俗点讲,计算机系统对基本类型的数据在内存中存放的位置有限制,系统会要求这些数据的首地址的值是某个数(这个数一般为4或者8的)的倍数,这就是所谓的内存对齐

而32位机器上默认的对齐模数一般为4,64位机上位8。

在结构体中,成员数据对齐满足以下规则:

  • 结构体重的第一个成员的首地址即时结构体变量的首地址。
  • 结构体中的每一个成员的首地址相对于结构体IDE首地址的偏移量是该成员数据类型大小的整数倍。
  • 结构体的总大小是对齐模数(对齐模数等于#pragma pack(n)所指定的n与结构体重最大数据类型的成员大小的最小值)的整数倍

Example:

1
2
3
4
5
6
7
8
9
10
struct One{
double d;
char c;
int i;
}
struct Two{
char c;
double d;
int i;
}

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
2
3
class B:public A{
int a;
};

sizeof(B)的结果为4而不是5或8。

静态数据成员类型

1
2
3
4
class C{
int a;
static int b;
};

sizeof 结果为4,静态数据成员被存放在类对象之外。

带非虚函数成员的类

1
2
3
4
5
class D {
public:
void func1() {}
static void func2() {}
};

sizeof(D)结果为1,无论是普通成员函数还是静态成员函数都被存放在类对象之外。

带虚函数成员的类

1
2
3
4
class E {
public:
virtual void func() {}
};

sizeof(E)结果为8,带虚函数成员的类对象会包含一个指向该类的virtual table的指针。

普通派生类

1
2
3
class G : public C {
int a;
};

sizeof(G)的结果为8,派生类会存放基类中非静态数据成员(C中的a)的副本。

基类带虚函数的派生类

1
class H : public E {};

sizeof(H)结果为8,由于基类中带虚函数,派生类中也必须保存一个指向派生类的virtual table的指针。

多重继承的派生类

1
2
3
4
5
6
7
8
9
10
class E1 {
public:
virtual void func() {}
};
class E2 {
public:
virtual void func() {}
};

class E3 :public E1, public E2{};

sizeof(E3)结果为16,子类中保存了两个virtual table的指针

虚继承的派生类

1
2
3
4
5
6
7
class H1{};

class H2{
int a{};
};

class H3:public virtual H1, public virtual H2{};

sizeof(H3)的结果为16,是两个基类中的virtual table指针

普通类的对齐规则

1
2
3
4
class F {
char a;
int b;
};

sizeof(F)的结果为8而不是5,由于F的最大对齐值为4(int),因此a和b之间被补齐3字节。

多重继承下的对齐规则

1
2
3
4
5
6
7
8
9
10
11
12
13
class G1{
long l;
int a;
char *b;
virtual void func(){};
};
class G2{
int a;
char b;
virtual void func(){};
};

class G3:public G1, public G2{};

sizeof(G3)的结果为48,默认对齐模数为8的情况下
G1 = (8)+(4)+(8)+(8)
G2 = (4+4)+(1+7)+(8)
G3 = 48