啊我摔倒了..有没有人扶我起来学习....
@TOC
前言
结构体内存对齐历来是C语言学习过程中的重点,其目的是通过牺牲空间来换取时间,但是理解起来一点也不难,那我们就,学学?
一、结构体内存对齐是什么?
这是一种结构体存储在内存里的一套规则,具体如下:
- 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处
- 从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处
==对齐数:结构体成员自身大小和默认对齐数的较小值==
VS:8
Linus环境默认不设对齐数(对齐数是结构体成员的自身大小)
- 结构体的总大小,必须是最大对齐数旳整数倍
每个结构体成员都有一个对齐数,==其中最大==的对齐数就是最大对齐数
- 如果嵌套了结构体的情况:
嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
好了,是不是啥都没看懂?别慌,下面开始正餐
二、认识结构体内存对齐
1. 先来一个思考
#include<stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
- 路飞:以上代码的输出结果是什么?
- 贝吉塔:
- 路飞:那让我们运行代码看看!
- 贝吉塔:??????????
- 贝吉塔:这是怎么回事?!
2. 用offsetof协助一手
- 为了让我们深入了解,我们先学一个宏:
#include <stddef.h>
offsetof
- 它是用来计算结构体成员相对于起始位置的偏移量。起始位置就是结构体在内存中刚开始存储的那个字节,我们假设为0,偏移量就是相对于0偏移几个字节,比如偏移量是3。
- 我们用它计算一下
#include <stdio.h>
#include <stddef.h>
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", offsetof(struct S1,c1));
printf("%d\n", offsetof(struct S1,i));
printf("%d\n", offsetof(struct S1,c2));
return 0;
}
- 输出结果为
成员 | Value |
---|---|
c1 | 0 |
i | 4 |
c2 | 8 |
- 那我们就来看看究竟是怎么一回事~
3. 研究对齐规则
- 根据上述结果,struct s1的成员一共占12个字节,结合偏移量,那么它们在内存中应该是这样存放的
- 说明其中有些空间是浪费掉的
- 我们继续观察上图:
- 参考内存对齐规则1(结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处)得出,c1确实在0偏移量处
- 参考内存对齐规则2(从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处),由于博主使用的是VS,默认对齐数为8,而每个成员自身大小与默认对齐数进行比较,较小值就是该成员的对齐数了,因此列出下表
成员 | 自身大小 | 默认对齐数 | 对齐数(较小值) |
---|---|---|---|
c1 | 1 | 8 | 1 |
i | 4 | 8 | 4 |
c2 | 1 | 8 | 1 |
因此,i的对齐数为4,所以它存储的时候不能紧接在c1的背后,只能从偏 移量为4(对齐数的整数倍)处开始存储,往后占据4个字节
- 然后c2就紧接在i后边存储,此时偏移量为8,因为c2的对齐数是1嘛,偏移量8不就是1的整数倍嘛
- 再参考内存对齐规则3(结构体的总大小,必须是最大对齐数旳整数倍)可知,要先在结构体成员中找出对齐数的老大,struct S1里老大是4,那么4就是最大对齐数,此时结构体总大小就得是4的整数倍,而此时c2存储完之后是在偏移量为8处,struct S1一共占用了9个字节,所以必须往后开辟空间,即使浪费也在所不惜
那么用struct S2练练手吧,计算出结构体的大小~
struct S2
{
char c1;
char c2;
int i;
};
答案是8
趁热打铁,我们继续!
struct S3
{
double d;
char c;
int i;
};
答案是16
扶我起来!我还要继续
struct S4
{
char c4;
struct S3 s3;
double d4;
};
- 这个就有点难度了,不怕,我们还有对齐规则4(嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍),看起来挺绕,其实说白了都是找最大对齐数,细心一点就很容易了
此时偏移量8至23存储的其实就是s3,只是s3里面又存储了d、c、i
答案是32
三、为什么要有结构体内存对齐?
1. 存在的必要
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
针对性能原因我们试着解释一下
假设每次访问内存4个字节,不对齐的话访问i时需要两次
- ==所以说结构体内存对齐是用空间换时间的做法==
2. 修改默认对齐数
- 假如你就是不想浪费空间,咱可以用下列代码取消默认对齐数
#pragma pack(1)
- 其实原理就是设置括号里的数字为新的默认对齐数,设为1的话相当于没有对齐的概念,所以同样可以设成其他数字(一般是2的整数倍),但是使用后想恢复的话,再用一次
#pragma pack()
- 此时括号里不填
四、小小的总结
其实多尝试比较的话是可以看出来,拥有同样成员的结构体,所占用的内存是不一样的
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
刚刚一起分析过的,struct S1大小是12,struct S2是8,通过观察,以后写结构体,==尽量把占用内存较小的变量放在前面==