结构体字节对齐能减少内存占用,提高内存访问效率。不论在x86或ARM处理器上C的每种类型存储都会要求内存对齐,除char
以外。结构体中不能包含结构体本身,但可以包含指针。由于平台原因对齐大小不同,可能同一个结构体得到的大小会不同,但一般的对齐规则如下:
chars
可以从任何字节地址开始,2字节shorts
必须从偶数地址开始,4字节的ints
或floats
必须从被4整除的地址开始,而8字节的longs
或doubles
必须从被8整除的地址开始。- 从偏移量为0的地址处开始,尽量按照同类型存储,减少内存的空白。
- 结构体总大小为所有成员中最大对齐数的整数倍。
- 如果嵌套了结构体,结构体的整体大小是包括嵌套结构体的成员在内的所有最大对齐数的整数倍(可以想成把嵌套结构体inline)。
在64位AMD机器上,对于如下结构体:
struct t{
char str;
int val;
double a;
};
int main(){
printf("%lu\n",sizeof(struct t));
return 0;
}
char占1字节,int占4字节,double占8字节,但结构体大小并不是1+4+8=13
,而是16
,原因是:char存放于开始占1字节,int是占4字节的,根据规则他只能存放在4的倍数的地址,因此存放在4号地址,同理double刚好接着在8号地址存放,至此,再看当前结构体大小是不是最大对齐数8的整数倍,是则为当前结构体不小,不是整数倍则调整为整数倍。示意如下:
在64位AMD机器上,假设如下带有嵌套结构体的结构体:
struct si{
long pot; //8 字节
int or; //4 字节
long *x; //8 字节
};
struct t{
char str; //1 字节
struct si *st; //8 字节
struct si s; //24 字节
};
int main(){
printf("%lu\n",sizeof(struct si));
printf("%lu\n",sizeof(struct t));
return 0;
}
运行以上代码得到结果:24 40。
按照对齐规则,第一个结构体的起始地址存放pot,or从8号位置开始存储,因为它正好存放在它的倍数的地址上,此时存储到12号地址,但是*x需要从它的倍数地址16号地址开始存储8字节到24,24正好也是最大对齐数8的整数倍,所以第一个结构体的大小为24。对于第二个结构体同理,str存放起始地址,指针8字节从8号地址存放到16号地址,s接着存放,所以大小为40。
如上所示的结构体struct t
,它一开始存储较小的数据类型,然后存储较大的数据类型,导致中间产生很多的空白空间,对于一些结构体的整体大小也会变大,解决这种问题最简单的方式就是进行reorder
,使所有与指针对齐的字段都放在首位,在64位机器上,它们将是8个字节,然后是4字节的类型,然后是2字节的类型,最后是字符类型。
对于以下结构体f,对其进行reorder之后得到的结构体f_reorder:
struct f{
char c;
struct f *f;
short x;
double d;
};
struct f_reorder {
struct f_reorder *f;
double d;
short x;
char c;
};
int main(){
printf("%lu\n",sizeof(struct f));
printf("%lu\n",sizeof(struct f_reorder));
return 0;
}
运行以上代码得到结果:32 24。
进行reorder
之后结构体整体大小变小,而且在内存的存储填充的更好。reorder
也不是总能带来正收益,比如reorder之后可能打乱了冷热的结构,反而带来了负收益,此时需要结合structure进行split
或是peeling
等操作。
了解关于结构体布局优化相关的内容,可参考:编译优化之 - 结构数据布局优化入门
References:
- http://www.catb.org/esr/structure-packing/#_awkward_scalar_cases