前言:
本篇重点为结构体内存对齐与位段的概念,该知识点是一个非常热门的考点,相信本篇文章会给你带来收获,博主会持续更新C语言的进阶知识,感兴趣的读者可以关注博主动态🌟🌟🌟
一、结构体
(1)结构体的特殊声明
我们知道结构体的声明大概为下面的方式:
struct tag { member - list;//成员变量列表 }variable - list;//变量列表 struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; //分号不能丢
在声明结构的时候,可以不完全的声明。
比如:
//匿名结构体类型 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], * p;
上面的两个结构在声明时省略了结构体标签tag,这种被称为匿名结构体。
我们发现两个结构体的成员变量是完全一致的,那么我们可不可以认为上面的两个结构体是相同类型的呢?
//在上面代码的基础上,下面的代码合法么?
p = &x;
答案是不能,因此该代码也不合法,原因在于编译器会把上面的两个声明当成完全不同的两个类型。
(2)结构体的自引用
这里的自引用指的是指针,即结构体中可以包含该结构体类型的指针,如:
struct Node { int data; struct Node* next; };
而不可以是:
struct Node { int data; struct Node next; }; //err
原因在于其自身类型大小无法计算
即sizeof(struct Node)是多少? 你会发现他会无穷的一直计算下去。
刚才学习了匿名结构体,那么我们看下面的代码是否可行:
//代码3 typedef struct { int data; Node* next; }Node;//err
答案是不行的,原因在于该匿名结构体省略了结构体标签tag,
也就是说,Node的声明在Node* next之后,即还没有创建Node,就要使用。
(3)结构体嵌套初始化
struct Point { int x; int y; }p1; struct Node { int data; struct Point p; struct Node* next; }n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化 struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化
像上面这种结构体中包含了另一个结构体初始化的情况就叫做结构体嵌套初始化。
(4)结构体内存对齐
接下来我们来学习如何计算结构体的大小,其涉及到了结构体内存对齐的概念,也是本篇最为重要的内容。
首先给出两个结构体,你认为他的大小为多少?
//练习1 struct S1 { char c1; int i; char c2; };//6? //练习2 struct S2 { char c1; char c2; int i; };//6?
在学习结构体内存对齐前, 你可能认为两个都为6,但是实际上为12和8。
也就是说结构体中有的成员变量之间存在空隙,浪费了一定的空间。
那么空隙如何计算呢?
宏:offsetof( type, member )(函数不能传参类型,但宏可以)
使用此宏需引用头文件stddef.h,该宏可以计算结构体成员相较于结构体起始位置的偏移量。
如图:
我们发现结构体大小的计算不是那么简单,那么结构体大小遵从什么样的规则呢?
结构体的对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数=编译器默认的一个对齐数与该成员大小的较小值。
这里数组比较特殊,它的大小为数组元素类型的大小,而不是整个数组的大小,比如char c[5],他的大小为1,若默认对齐数为8,则对齐数取较小为1。
VS中默认的值为8。gcc编译器无默认对齐数,对齐数就是结构体成员自身大小。
学习了如何计算,那我们来尝试两道题:
//练习1 struct S1 { double d; char c; int i; }; //练习2-结构体嵌套问题 struct S2 { char c1; struct S1 s1; double d; };
答案:S1大小16,偏移量分别为0、8、12;
S2大小32,偏移量分别为0、8、24。
练习1分析:
练习2分析: