C语言:结构体的内存对齐方式
结构体内存对齐
- 1、引子
- 2、内存对齐
- 2.1、结构体成员的偏移量
- 2.2、结构体内存对齐规则
- 2.3、结构体内存对齐原因
- 2.4、修改默认对齐数
1、引子
关于结构体的内存规则这一话题的研究我们可以通过计算结构体大小来引入。
例题:
#include<stdio.h>
struct stu1
{
char a;
char b;
int n;
};
struct s
{
char a;
int n;
char b;
};
int main()
{
printf("stu1:%zd\n", sizeof(struct stu1));
printf("s: %zd\n", sizeof(struct s));
return 0;
}
从题中可以看出,结构体stu1和s用有相同的变量,只不过它们的创建顺序不一样,大家认为它们的结果各是多少呢?
结果:
只是创建顺序发生了变化,它们的结构体大小就不一样,所以我们可以推断,这两中结构体的内存创建过程肯定是不一样的。
2、内存对齐
2.1、结构体成员的偏移量
什么是偏移量呢?
偏移量是指变量距离首地址的距离,运用到结构体上就是结构体各变量距离结构体起始位置的距离。
在C语言中有一个宏,它可以计算偏移量,他就是offsetof,它在<stddef.h>头文件下,我们可以使用它来看一下。
以stu1为例
代码:
#include<stdio.h>
#include <stddef.h>
struct stu1
{
char a;
char b;
int n;
};
int main()
{
printf("a:%d\n",offsetof(struct stu1,a) );
printf("b:%d\n", offsetof(struct stu1,b));
printf("n:%d\n", offsetof(struct stu1,n));
return 0;
}
结果:
那这个结果又能说明什么问题呢?
我们可以画图说明:
我们根据它的偏移量可以画出这样一幅图,我们从图中可以发现0-7一共是8个字节,正好符合我们的结果。
我们从上图也不难发现,我们浪费了两个字节,这就和结构体的对齐规则有关。
2.2、结构体内存对齐规则
1、结构体的第一个成员对齐到和结构体变量的起始位置偏移量为0的地址处。详情可以看上图的变量a
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数: 编译器默认的一个对齐数(vs2022中默认是8)与该成员变量字节大小中的较小数。
(例如:在vs2022中假如在结构体中有int n,它是4个字节,比8小,那它的对齐数就是4).
3.结构体总大小为最大对齐数的整数倍
4、如果结构体中嵌套了结构体,结构体的整体大小,就是所有最大对齐数的整数倍(含嵌套结构体中的成员的对齐量)
练习1:
struct stu
{
double s;
char c;
int n;
};
结果:16
其中它们最大的对齐数是8,而全部放入后总大小是16,是8的整数倍,不用改变。
练习2:
struct stu
{
double s;
int n;
char c;
};
结果:16
虽然也是16,但是情况不同:
这种情况下,全部创建完成之后是13个字节,但最大对齐数是8,13不是8的整数倍,所以答案是16.
练习3:
#include <stdio.h>
struct s1
{
int m;
char g;
short k;
};
struct stu
{
double s;
struct s1;
char c;
};
int main()
{
printf("%zd", sizeof(struct stu));
return 0;
}
结果:24
这个就留给大家思考,我什么问题,在评论区留言,博主看到后会回复的。
2.3、结构体内存对齐原因
至于为什么会存在内存对齐
1.平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台上只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次。假设一个处理器总是从内存中取8个字节,那么地址必须是8的倍数。如果我们能保证将所有double类型数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读值或写值了,否则,我们可能要执行两次内存访问,因为对象可能被放在两个8字节内存块中。
总的来说:结构体的内存对齐,是拿空间来换取时间的。
2.4、修改默认对齐数
在C语言中默认对齐数也可以被修改。
#pragma pack(4)
这样就把默认对齐数修改为4了,再恢复之前的默认对齐数也很简单,只需再次添加语句就好只不过这次括号里不用添加数字。
#pragma pack()
这样就取消了。
好了,本期博客到这里就讲完了,
有什么疑问或者有什么看法在评论区留言哦,看到后会回复的。
我们下期博客再见~
(~ ̄▽ ̄)~