前言
本文中所有示例代码都是在64位linux系统下使用gcc编译。
目录
1、默认对齐规则
1. 第一个成员变量的偏移地址为0;
2. 其他成员变量要对齐到自身大小整数倍的地址处;
3. 结构体总大小为其成员变量所含最大类型的整数倍;
4. 嵌套的结构体对齐到自己成员变量所含最大类型的整数倍处,结构体的总大小是所有成员变量所含最大类型(含嵌套结构体)的整数倍。
例1:非嵌套结构体。
struct S1
{
char c;
int i;
double d;
}
分析:
根据规则1,变量c的偏移地址是0,占用1个字节;
根据规则2,变量i的偏移地址是4,占用4个字节;变量d的偏移地址是8,占用8个字节;
因此,结构体S1一共占用16字节。
例2:嵌套结构体。
struct S1
{
char c;
int i;
double d;
}
struct S2
{
char c1;
struct S1 s1;
double d1;
}
分析:
根据规则1,变量c1的偏移地址是0,占用1个字节;
根据规则4,子结构体s1所含最大类型是double(占用8字节),因此,s1的偏移地址是8,又由上面的例1可知,s1占用16个字节;
根据规则2,变量d1的偏移地址是24;
因此,结构体S2一共占用32字节。
思考:为什么要进行内存对齐呢?
原因有以下两点:
1、平台原因
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因
数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因在于,访问未对齐的内存时,处理器需要进行两次内存访问,而对于对齐的内存,只需要访问一次。
(注:图片取自此篇参考文章) [C语言](详细)结构体的内存对齐(规则、存在原因、默认对齐数的修改等+实例分析)_结构体内存对齐规则-CSDN博客
2、修改对齐规则
结构体的内存对齐可以理解成是拿空间换取时间,但是,在开发时我们为了节省内存空间,有时并不想让结构体按照上述规则进行默认对齐。修改对齐规则的方式有以下两种。
2.1 #pragma pack(n)
在需要修改对齐方式的结构体前面加上 #pragma pack(n),后面加上 #pragma pack(),即可让编译器按照n字节进行对齐。
例1:手动对齐数小于默认对齐数。
(1)默认对齐:占用8字节
struct S1
{
char c;
int i; //i的偏移地址为4
}
(2)手动对齐:占用5字节
#pragma pack(1) //修改对齐数为4
struct S1
{
char c;
int i; //i的偏移地址为1
}
#pragma pack() //结束修改对齐数
但是要注意,n应该小于默认对齐字节数。如果大于的话,编译器还是会按照默认对齐数进行对齐。也就是说,编译器会选择手动设置的对齐数和默认对齐数中的较小值。
例2:手动对齐数大于默认对齐数
(1)默认对齐:占用4字节
struct S1
{
char c;
short s; //s的偏移地址为2
}
(2)手动对齐:占用4字节
#pragma pack(4) //修改对齐数为4
struct S1
{
char c;
short s; //s的偏移地址仍然为2,不会修改为4
}
#pragma pack() //结束修改对齐数
2.2 __attribute__((packed))
__attribute__((packed)) 可以取消结构体内存对齐,按照最紧凑的方式排列。和 #pragma pack(1)的效果一致。
(1)默认对齐:占用8字节
struct S1
{
char c;
int i; //i的偏移地址为4
}
(2)手动对齐:占用5字节
//取消内存对齐
struct __attribute__((packed)) S1
{
char c;
int i; //i的偏移地址为1
}
3 位域结构体
3.1 相邻位域类型相同
例1:相邻位域类型相同,位宽之和小于类型大小。进行压缩,后面字段紧邻前面字段。
(1)默认对齐:占用1个字节
struct test
{
char a:4;
char b:2;
char c:2;
}
(2)手动对齐:占用1个字节
#pragma pack(1)
struct test
{
char a:4;
char b:2;
char c:2;
}
#pragma pack()
例2:相邻位域类型相同,位宽之和大于类型大小。不进行压缩,后面字段从新的存储单元开始。
(1)默认对齐:占用3个字节
struct test
{
char a:6;
char b:6;
char c:4;
}
(2)手动对齐:占用2个字节
#pragma pack(1)
struct test
{
char a:6;
char b:6;
char c:4;
}
#pragma pack()
3.2 相邻位域类型不同(和编译器有关)
例3: 相邻位域类型不同,位宽之和大于类型大小。进行压缩,后面字段紧邻前面字段。
(1)默认对齐:占用4个字节(规则3)
struct test
{
char a:7;
int b:11;
int c:4;
int d:2;
}
(2)手动对齐:占用3个字节
#pragma pack(1)
struct test
{
char a:7;
int b:11;
int c:4;
int d:2;
}
#pragma pack()
例4:相邻位域类型不同,位宽之和小于类型大小。进行压缩,后面字段紧邻前面字段。
(1)默认对齐:占用4个字节(规则3)
struct test
{
int a:10;
char b:6;
}
(2)手动对齐:占用2个字节
#pragma pack(1)
struct test
{
int a:10;
char b:6;
}
#pragma pack()
3.3 位域和非位域穿插
例5:位域和非位域穿插时,不进行压缩,后面的字段从新的存储单元开始。
(1)默认对齐:占用3个字节
struct test
{
char a:6;
char b;
char c:2;
}
(2)手动对齐:占用3个字节
#pragma pack(1)
struct test
{
char a:6;
char b;
char c:2;
}
#pragma pack()
注意:对于例5,如果需要进行压缩,可以将【char b;】修改为【char b:8;】,则结构体占用字节数从3字节压缩为2字节。
例6:位域和非位域穿插时,不进行压缩,后面的字段从新的存储单元开始。
(1)默认对齐:占用12个字节(规则2和规则3)
struct test
{
char a:6;
int b;
char c:2;
}
(2)手动对齐:占用6个字节
#pragma pack(1)
struct test
{
char a:6;
int b;
char c:2;
}
#pragma pack()
参考文章
C语言 | 关于结构体内存对齐,看这篇就够了-腾讯云开发者社区-腾讯云 (tencent.com)
[C语言](详细)结构体的内存对齐(规则、存在原因、默认对齐数的修改等+实例分析)_结构体内存对齐规则-CSDN博客
C/C++编程笔记:C语言对齐问题【结构体、栈内存以及位域对齐】 - 哔哩哔哩 (bilibili.com)
C语言字节对齐及__attribute__((aligned(n))) 与 #pragma(pack(n))的作用-CSDN博客