C 中结构体对齐
参考
百度百科内存对齐
对齐作用
可以使得以最少的次数将操作数加载到寄存器中,如果数据没有对齐,则当CPU以最小读取数据大小从内存读入数据时可能只取到了一部分数据,而对齐情况下可以一次读入。
对齐修改
在程序中可以通过pragma pack(x)
指定对齐大小,x即为需要指定的对齐大小。默认情况下32位平台采用4字节对齐,64位平台采用8字节对齐
对齐规则
摘自百科,感觉概括的比较好。
规则一:成员对齐
数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
#include <stdio.h>
struct A {
char a;
int b;
long c;
};
#define OFFSET(a, b) (int)((char*)&(a.b) - (char*)&a)
int main()
{
struct A x;
printf("sizeof %u\n", (int)sizeof(x));
printf("offset a: %u, offset b: %u, offset c: %u\n", OFFSET(x, a), OFFSET(x, b), OFFSET(x, c));
return 0;
}
在64位平台上编译运行输出入:
sizeof 16
offset a: 0, offset b: 4, offset c: 8
内存中的结构体数据分布如下:
- char类型成员a位于结构体第一个位置,offset为0,占用1-byte空间
- int类型成员b位于结构体第二位置,offset需要根据类型大小和当前对齐大小决定,sizeof(int) = 4, 而64位默认以8字节对齐,取两者小的,即按4字节对齐,从成员a后找到第一个能被4整除的位置,即offset=4
- long类型成员需要按8字节对齐,而此时b成员后的offset刚好为8,满足要求直接将成员c放置在成员b后即可。
如果有以下结构体:
struct A {
char a;
int b;
char c;
long d;
};
则在64位平台下其(sizeof(struct A))大小为24 bytes,成员分布如下
可以看到由于char类型成员c的存在,使得long类型成员前需要有大量空白空间才能满足对齐要求,浪费了比较多的空间。如果将成员b和c调换顺序则,只需要16-bytes内存空间。
规则二:结构体补齐
结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
即像如下的结构体:
struct A {
int a;
char b;
};
虽然成员b后面没有再有其他成员用不上规则一,但是仍然需要应用结构体末尾的补齐规则。这个结构体中最大宽度的成员为int型大小小于64位默认对齐大小,所以按4字节补齐,其成员分布如下:
64位环境下其sizeof结果为8
推论
结合1、2可推断:当#pragma pack(n)的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
空间分配对齐
由于结构体中的起始成员的偏移都是0,所以结构体是否真的对齐并达到性能优化除了结构体内部成员的这些对齐规则,还取决于结构体在内存中的起始位置。如果将结构体分配到一个奇数地址上,那么成员内部做的这些对齐工作都白费了。所以结构体变量都会被对齐到特定的地址。这个特定地址是结构体最宽数据类型大小和pack值的较小者的整数倍。
结构体嵌套
当出现结构体嵌套时,内部的结构体按照其中最长宽度类型长度和pack值的较小者对齐(这跟空间分配时的对齐非常像)。如
struct Inner {
char xa;
int xb;
};
struct A {
char a;
struct Inner b;
};
#define OFFSET(a, b) (int)((char*)&(a.b) - (char*)&a)
int main()
{
struct A x;
printf("sizeof %u\n", (int)sizeof(x));
printf("offset a: %u, offset b: %u\n", OFFSET(x, a), OFFSET(x, b));
return 0;
}
64位环境下输出如下:
sizeof 12
offset a: 0, offset b: 4
当将Inner结构体中的xb成员换成long类型时
struct Inner {
char xa;
long xb;
};
struct A {
char a;
struct Inner b;
};
64位平台输出如下:
sizeof 24
offset a: 0, offset b: 8
当通过#pragma pack(4)指定默认对齐大小为4时,情况又会发生变化
#pragma pack(4)
struct Inner {
char xa;
long xb;
};
struct A {
char a;
struct Inner b;
};
64位平台输出结果为:
sizeof 16
offset a: 0, offset b: 4