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