对于图片的解释:
- d是第一个成员,所以它直接放在
偏移量为0的位置- c是第二个成员,它的对齐数是1
任何一个数都是1的倍数,所以
c紧接着放在d内存的后面- i 是第三个成员,它的对齐数是4
而c的后面是9, 9不是4的倍数
10也不是4的倍数,直到12才是
4的倍数,所以i从12开始放- 最后一个成员放完后的位置是15
而结构体最大对齐数是8
15不是8的倍数,16才是
所以最终在16停止
3.4 回头验证最初的数据
最开始的两个结构体:
struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };
1. 对于S1而言是这样的情况:
这也就解释清楚为什么会打印12出来的
对于S2而言是这样的情况:
占8个字节也解释清楚了!
4. 存在内存对齐规则的原因
- 原因1:平台原因(移植原因):
不是所有的硬件平台都能访问
任意地址上的任意数据的
某些硬件平台只能在某些地址处取
某些特定类型的数据,否则抛出硬件异常
- 原因2:性能原因
如果不存在内存对齐的话
平台4个字节4个字节的访问
int类型的数据时有可能需要
读取两次才能取到一个数据
总的来说:内存对齐是拿空间换取时间
- 节省空间的技巧:
发现同样的成员类型和数量
成员放的位置不同,结构体大小
也存在的很大的区别
在写结构体时尽量将占用空间
小的数据集中在一起能节省空间
5. 位段
位段和结构的声明非常相似
但又存在下面这两个不同:
- 位段的成员必须是整型家族(int/char)
- 位段的成员名后边有一个冒号和一个数字
比如:定义一个位段
struct A { int _a:2; int _b:5; int _c:10; int _d:30; };
这个位段的大小是多少呢?
肯定不会是4×4=16个字节这么简单
printf("%d\n",sizeof(struct A));
结果是8,我们来简单分析一下:
5.1 位段的内存分配规则
位段是具有不确定性的,不能跨平台
它在每一个编译器下可能有所不同
我只介绍在VS编译器下的具体规则:
先初始化一下结构体:
s.a = 3; s.b = 12; s.c = 3; s.d = 4;
冒号后面的数字代表
变量所占的二进制位(比特位)
画图解释:
6. 联合(共用体)
联合也是一种自定义类型
它其中的变量共用同一份空间!
它的不同:
- 成员共用同一份空间
- 不用struct定义,而是用union定义
- 联合的大小至少是最大成员的大小
比如:
union Un { int i; char c; }; union Un un;//定义联合变量 printf("%d\n", &(un.i));//它们的地址相同 printf("%d\n", &(un.c));//共用同一份空间
6.1 联合大小计算
联合共用体和结构体一样
有内存对齐原则,不过联合的比较简单:
- 联合的大小至少是最大成员的大小
- 最大成员大小不是最大对齐数的整数倍时
就要对齐到最大对齐数的整数倍
比如:下面这两个联合
union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; printf("%d\n", sizeof(union Un1)); printf("%d\n", sizeof(union Un2));
它们的大小分别是:8和16
7. 总结以及拓展
结构体的内存对齐是面试的常考点!
掌握它不仅可以更深层次了解C语言
还可以在面试的时候给面试官一个震撼
拓展:修改默认对齐数
C语言提供了#pragma指令
来帮助我们解决这个问题:
假设我们想要将默认对齐数改为1:
#pragma pack(1)//设置默认对齐数为1 struct S1 { char c1; int i; char c2; };
假设我们又想修改回来:
#pragma pack(1)//设置默认对齐数为1 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 struct S2 { char c1; int i; char c2; };
上述代码中,结构体S1
使用的是默认对齐数为1
而S2使用的默认对齐数是8
拓展:利用联合求大小端
详细可以参考以下这篇文章:
🔎 下期预告:动态内存管理 🔍