文章目录
- 前言
- 一、结构体对齐规则
- 二、结构体大小计算 - 三板斧
- 一板斧
- 二板斧
- 三板斧
- 三、为什么存在内存对齐?
- 四、修改默认对齐数
前言
我们知道,整型变量有自己的大小,浮点型变量有自己的大小,数组也有自己的大小,只要数据存放到内存中,就会占用内存大小。
所以作为C语言数据类型的一种——结构体 同样也有自己的大小。
要注意的是,结构体虽是多种数据类型的集合,但结构体的大小并不像我们想的那样简单地将每个结构体成员的大小相加就能得到的。
想要计算结构体的大小,需要先去了解计算结构体的规则!
一、结构体对齐规则
结构体的大小计算要遵循结构体的对齐规则
:
- 结构体的第一个成员永远都放在0偏移处。(即结构体的首地址处,即对齐到0处)
- 从第二个成员开始,以后的每个成员变量都要对齐到某个
对齐数
的整数倍的地址处。 - 当成员变量全部放进去后,结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍,如果不够,则浪费空间对齐。
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数
= 该结构体成员变量自身的大小 与 编译器默认的一个对齐数的较小值
。
注:
VS中的默认对齐数为8。gcc环境下,没有默认对齐数。不是所有编译器都有默认对齐数,当编译器没有默认对齐数的时候,成员变量的大小就是该成员的对齐数。
二、结构体大小计算 - 三板斧
举例:
struct S
{
double a;
char b;
int c;
};
一板斧
找出每个成员变量的大小将其与编译器的默认对齐数相比较,取其较小值为该成员变量的对齐数。
注:VS2022默认对齐数为8.
二板斧
根据每个成员对应的对齐数找出它们在内存中的相对位置
三板斧
通过最大对齐数确定最终该结构体的大小
通过上面的分析图我们可以知道:
紫部分(double a成员占用
)+红色部分(char b成员占用
)+黄部分(int ic成员占用
)+红色与黄色之间的白色部分(浪费掉了)
总共占用了16
个字节的内存空间。- 再将总共占用的内存空间(16字节)与结构体成员的最大对齐数(8字节)相比较,此时16正好是8的整数倍,所以该结构体在VS编译器下的大小就16个字节。
- 注意:如果成员变量占用的总字节个数不是其成员变量中的最大对齐数的整数倍,这时我们需要将其扩大到最大对齐数的整数倍。
三、为什么存在内存对齐?
-
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。 -
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:结构体的内存对齐是拿空间来换时间
的做法。
在设计结构体的时候,我们既要满足对齐,又要节省空间,应该:让占用空间小的成员尽量集中在一起。
四、修改默认对齐数
当结构体的对齐方式不合适的时候,我们可以自己更改默认对齐数,要修改编译器的默认对齐数,需要借助于以下预处理命令:
#pragma pack()
如果在该预处理命令的括号内填上数字,那么默认对齐数将会被改为对应数字。
如果只使用该预处理命令,不在括号内填写数字,那么会恢复为编译器默认的对齐数。
#include <stdio.h>
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char a;
int b;
char c;
};
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char a;
int b;
char c;
};
int main()
{
printf("%d\n", sizeof(struct S1));//结果为12
printf("%d\n", sizeof(struct S2));//结果为6
return 0;
}