目录
前言
本文主要介绍C语言中,结构体的对齐规则、如何改变对齐数,对齐规则对内存的影响以及如何减小结构体占用的内存。
提示:小编系统为64位CentOS系统,默认编译器默认对齐数为8字节,如果结构体成员的大小大于编辑器的默认对齐数时,要以编辑器的对齐数为准,即二者取小。
一、结构体对齐规则
1、结构体的总大小对齐规则
一个结构体的总共内存大小是其类型最大成员大小的整数倍。如果结构体的最后一个成员之后没有足够的空间来满足对齐要求,编译器会在结构体末尾添加填充字节。
#include <stdio.h> struct A{ int i; //4字节 char c; //1字节 }; int main() { printf("%d\n",sizeof(struct A)); return 0; }
运行结果为:
8
上面展示的例子中,结构体A中分别有两个成员,第一个成员为int型,占4个字节,第二成员为char型,占1个字节。根据结构体的总大小对齐规则,找出该结构体中类型最大的成员,即int型(4字节),所以结构体的总体大小必须为4字节的整数倍。将成员所占的字节数加起来为5个字节,但不是4的整数倍,所以要继续填充3个字节,一共8个字节,才能满足规则。
2、结构体成员的对齐规则
结构体的每个成员相对于结构体开头的偏移量是该成员大小的整数倍。如果成员的大小小于对齐要求,编译器会在成员之间插入填充字节以满足对齐要求。
偏移量:在计算机汇编语言中,偏移量是指存储单元的实际地址与其所在段的段地址之间的距离,也称为“段内偏移”或“有效地址”。它是程序的逻辑地址与段首地址的差值。
#include <stdio.h> struct B{ char a; // 1 byte int b; // 4 bytes short c; // 2 bytes }; int main() { printf("%d\n", sizeof(struct B)); return 0; }
运行结果为:
12
上面的例子中,结构体B一共有三个成员变量,其中int型(4字节)最大,根据规则一,最后结构体的总体大小应该为4的整数倍。其中第一变量a的偏移量为0,是其大小的整数倍;第二个变量b大小为4字节,即需要其偏移量为4的整数倍,所以b的偏移量为4,而a与b变量之间的空只能用字节填充,即填充三个字节。到此一共占了8个字节大小,第三个变量c大小为2个字节,同理,其偏移量需要为2的整数倍,由于前面刚好满足4字节对齐,所以偏移量为2的整数倍就自然满足了,即c的偏移量为8。到此一共占了10字节大小,但由于规则一,c变量后面还需要填充2个字节,一共12字节,满足4的整数倍。
3、数组和结构体的对齐规则
数组中的每个元素都要满足上述对齐规则,即数组的每个元素都相对于数组开头的偏移量是其大小的整数倍。对于嵌套的结构体,嵌套的结构体本身也要满足其内部的对齐规则,并且嵌套结构体相对于外部结构体的偏移量也要满足对齐要求。
#include <stdio.h> struct C{ char a[3]; // 3 bytes int b; // 4 bytes }; struct D{ char i; // 1 byte struct C c_t; // 8 bytes int j; // 4 bytes }; int main() { printf("%d\n", sizeof(struct C)); printf("%d\n", sizeof(struct D)); return 0; }
运行结果为:
8 16
上面例子中,结构体C中,第一个变量为char型数组,根据数组的特点,其三个元素偏移量必然对齐,且占三个字节。第二个变量为int型,4字节,偏移量为4的整数倍,即偏移量为4。满足规则一,规则二,所以结构体C大小为:8字节。
在结构体D中,存在嵌套的结构体C,根据上面的规则一二,你很有可能算出来结构体D的大小为24,但其实它为16。为什么呢?因为在找最大类型变量是不能将结构体c_t当作一个整体,应该将其拆分来看,即最大类型的变量为4字节,总体大小需是4的整数倍。然后再看成员对其,第一个变量和第二个变量之间填充7字节,最终一共16字节。
二、改变编译器对齐数(#pragma pack)
#pragma pack指令:是用来修改编译器对结构体成员的对齐方式的。具体来说,它可以用来修改结构体或联合体中成员的偏移量对齐数,以及整个结构体或联合体的总大小对齐数。使用 #pragma pack(n) 时,n 指定了新的对齐字节数。结构体的成员将按照 n 字节对齐,同时整个结构体的总大小也将按照 n 字节对齐。
#include <stdio.h> struct C{ char a[3]; // 3 bytes int b; // 4 bytes }; #pragma pack(1) struct D{ char a[3]; // 3 bytes int b; // 4 bytes }; #pragma pack(2) struct E{ char a[3]; // 3 bytes int b; // 4 bytes }; int main() { printf("%d\n", sizeof(struct C)); printf("%d\n", sizeof(struct D)); printf("%d\n", sizeof(struct E)); return 0; }
运行结果为:
8 7 8
在上面的示例中,三个结构体的成员一摸一样,在编译结构体C时,还是原来的编译器对齐数,但在编译结构体D时,编译器对齐数改为了1,根据开头提示,这时候规则一二均取对齐数1,所以结构体E的大小变成了7。在编译结构体E时,对于总体大小对齐而言,在4和2之间选择2;对于成员偏移量对齐而言,第一个变量在1和2之间选择1,对于第二个变量在2和4之间选择2。最终算出结构体E的大小仍为8。
三、如何减小结构体占用内存
1、 重新排列成员顺序
将占用空间小的成员放在前面,大的成员放在后面,并且尽量让相邻的成员类型相同,这样可以减少由于对齐而产生的填充字节。
如上述规则二中的例子,将其重新排列后为:
#include <stdio.h> struct B{ char a; // 1 byte short c; // 2 bytes int b; // 4 bytes }; int main() { printf("%d\n", sizeof(struct B)); return 0; }
运行结果为:
8
相比于原来的12,现在结构体的大小变为了8,减小了4字节的空间。
2、使用#pragma pack
指令
通过#pragma pack(n)
指令可以修改编译器默认的对齐字节数。将n
设置为一个较小的值可以减少由于对齐而产生的填充字节,从而减小结构体的总体大小。但是,这可能会影响程序的性能,因为非对齐的内存访问通常比对齐的内存访问要慢。
#include <stdio.h> struct C{ char a; int b; double c; }; #pragma pack(1) struct D{ char a; int b; double c; }; int main() { printf("%d\n", sizeof(struct C)); printf("%d\n", sizeof(struct D)); return 0; }
运行结果为:
16 13
相同成员的结构体,D比C小了3个字节。
3、使用位域
如果结构体中的某些成员只需要占用几个位,那么可以使用位域来定义这些成员。位域允许你将多个小成员打包到一个字节或更大的整数类型中,从而减少浪费的空间。
4、其他
检查结构体定义,去除那些实际上并不需要的成员,可以直接减小结构体的总体大小。如果可能的话,尽量使用占用空间更小的数据类型。例如,如果某个成员的值范围较小,可以考虑使用char
或short
代替int
。
总结
了解结构体的对齐规则对于提高内存使用效率、保证访问速度、避免硬件异常以及提高代码的可移植性都具有重要意义。在编写涉及结构体操作的代码时,我们应该了解和考虑这些对齐规则。
相关内容可以查看:C语言——结构体(Struct)详解+运用举例_struct在c语言中的用法-CSDN博客
有误之处望指正!!