结构体内存对齐
[toc]
一、什么是结构体内存对齐
进入讲解前,先看一段 C 代码:
1 |
|
思考一下 node1
和 node2
的大小分别为多少?
1 |
|
结果如下:
1 |
|
我是在 Windows 下 MinGW32 的 GCC 测试的
一样的成员属性,但 node1
只有 8K,而 node2
的大小却有 12K。
由此可见,结构体对齐,实质上就是内存对齐。
二、为什么要结构体内存对齐
为什么要结构体对齐,原因就是内存要对齐,原因是芯片内存的制造限制,是制造成本约束,是内存读取效率要求。
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,
- 为了访问==未对齐==的内存,处理器需要作==两次==内存访问;
- 而==对齐==的内存访问仅需要==⼀次==访问。
假设一个处理器总是从内存中取 8 个字节,则地址必须是 8 的倍数。如果我们能保证将所有的 double 类型的数据的地址都对齐成 8 的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个 8 字节内存块中。
总体来说,结构体的内存对齐是拿空间来换取时间的做法。
三、对齐系数
对齐系数是指编译器在内存布局中对结构体成员进行对齐的要求。由于硬件访问内存的方式有一定的要求,结构体的对齐方式会影响内存的使用效率和访问速度。
在大多数编译器中,结构体对齐系数是通过编译器的编译选项或者特定的指令来指定的。对齐系数通常以==字节==为单位,表示结构体成员的起始位置必须是该字节数的整数倍。
注意,不同的平台,模数是不一样的。
1 |
|
结果如下:
1 |
|
可知,对齐系数就是 8.
当然,对齐模数是可以改变的,可以用预编译命令 #pragma pack(n)
,n=1,2,4,8,16(必须是 2 的幂次方)来改变这一系数,其中的 n 就是你要指定的“对齐系数”。
现在将上面的结构体改变一下:
1 |
|
将它的对齐模数改为了 4,结果如下:
1 |
|
四、结构体对齐规则
- 结构体的内存大小,并非其内部元素大小之和;
- 结构体变量的起始地址,可以被最大元素基本类型大小或者模数整除;
- 结构体的内存对齐,按照其内部最大元素基本类型或者模数大小对齐;
- 模数在不同平台值不一样,也可通过
#pragma pack(n)
方式去改变,其中 n 一定是 2 的幂次方,如 1,2,4,8,16 等; - 如果空间地址允许,结构体内部元素会拼凑一起放在同一个对齐空间;
- 结构体内有结构体变量元素,其结构体并非展开后再对齐;
union
和bitfield
变量也遵循结构体内存对齐原则。
下面结合例子来看一下:
1、例一:文章开头的例子
对于本文一开始提到的例子,它们的内存对齐方式如下(这里就不以数组举例了):
1 |
|
解释如下:
node1
中的元素 a 是 int 类型,按 4 个字节对齐,其地址位是 4 的整数倍;而 b 和 c 就按 1 字节对齐,就跟在 a 后面就行了。
node2
中 b 是按 1 个字节对齐,放在最前面;而 a 是按 4 个字节对齐,其地址位必须是 4 的整数倍,所以,只能找到个 +4 的位置;紧接着 c 就按 1 字节对齐,跟其后面。
2、例二:稍微复杂的情况
1 |
|
结果如下:
1 |
|
其内存分布如下:
3、结合 union 和 struct
1 |
|
如果 union 里的元素类型不一样,那就以最大长度的那个类型对齐了。
4、结构体嵌套
1 |
|
1 |
|
可以看见,结构体内的结构体,结构体内的元素并不会和结构体外的元素合并占一个对齐单元。