补充一下结构体的知识。

结构体回顾

以前以为结构体的内存大小就是结构体中所有变量内存大小的总和。
但有次运行了下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct A {
int a; //4
char b; //1
double c; //8
};
struct B
{
char a;//1
int b;//4
double c;//8
char d;//1
}
int main() {
std::cout << "sizeof(A) = " << sizeof(A) << std::endl;
std::cout << "sizeof(B) = " << sizeof(B) << std::endl;
return 0;
}

输出结果

1
2
sizeof(A) = 16
sizeof(B) = 24

显然,如果只是将结构体中所有变量的内存大小相加,A应该是13,B应该是14啊,真是奇怪。
原因就是为了提高内存的访问效率,C中的结构体存在内存对齐

内存对齐

对齐规则

  • 成员对齐:第一个成员变量从0位置开始占用内存空间,其余每个成员的起始地址必须是其对齐数的整数倍
    对齐数 = min(成员类型的大小,编译器默认对齐数)
    编译器默认对齐数可由#pragma pack(n)修改
  • 结构体总大小:结构体的总大小是所有成员 最大对齐数的整数倍
  • 结构体作为成员时:当结构体作为其他结构体成员时,其起始地址是自身最大对齐数的整数倍。

为什么要内存对齐

为什么要内存对齐:
主要是由于 CPU 的访问内存的特性决定,CPU 访问内存时并不是以字节为单位来读取内存,而是以机器字长为单位,实际机器字长由 CPU 数据总线宽度决定的。实际 CPU 运行时,每一次控制内存读写信号发生时,CPU 可以从内存中读取数据总线宽度的数据,并将其写入到 CPU 的通用寄存器中。比如 32 位 CPU,机器字长为 4 字节,数据总线宽度为 32 位,如果该 CPU 的地址总线宽度也是为 32 位,则其可以访问的地址空间为 [0,0xffffffff]。内存对齐的主要目的是为了减少 CPU 访问内存的次数,加大 CPU 访问内存的吞吐量。假设读取 8 个字节的数据,按照每次读取 4 个字节的速度,则 8 个字节需要 CPU 耗费 2 次读取操作。CPU 始终以字长访问内存,如果不进行内存对齐,很可能增加 CPU 访问内存的次数。

实践

回顾

再来看看A和B两个结构体。
A的内存大小 = 4+1+3(补齐的)+8 = 16
B的内存大小 = 1+4+8+1+7(补齐的)= 21 所有成员最大对齐数为8,结构体大小补全为24

小练习

如果调换一下A中变量的顺序,如下:

1
2
3
4
5
struct C {
int a; //4
double c; //8
char b; //1
};

结构体C的内存大小又是多少呢?

答案是24!
原因:4+8+1+7(补齐的)= 20,所有成员最大对齐数为8,结构体大小补全为24