目录
今天我们就要来学习一下考试经常考察的结构体内存对齐问题,相信有很多同学都被这个问题恶心过🤢,不过没有关系今天我会给你们讲清楚的,请各位系好安全带,我们要开始出发啦😃
大部分的参考资料都是这样说的:
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要?次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。?
介绍完原因后,接下来是结构体内存对齐的规则(重中之重,这里一定要弄懂啊!!!)
1.结构体的第?个成员对?到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对?到某个数字(对?数)的整数倍(0,1,2...)的地址处。
对?数=编译器默认的?个对?数与该成员变量??的较?值。
- VS 中默认的值为 8
- Linux中 gcc 没有默认对?数,对?数就是成员??的??
3. 结构体总??为最?对?数(结构体中每个成员变量都有?个对?数,所有对?数中最?的)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对?到??的成员中最?对?数的整数倍处,结构体的整体??就是所有最?对?数(含嵌套结构体中成员的对?数)的整数倍。?
接下来为了帮助大家理解下面我给出几个实例大家可以自己手算
因为c1是char类型1个字符,系统给的对齐数为8,和1相比取小的,c1的对齐数为min(1,8)所以在哪开始都可以,i是int类型4个字节要从它的整数倍开始,所以只能从min(4,8)开始,c2在哪都可以开始,最后不要忘了第三点结构体总??为最?对?数(结构体中每个成员变量都有?个对?数,所有对?数中最?的)的整数倍。这里特别注意是总大小,不是最大下标,是最大下标加一。故最后下标到11位置,图解如下:
c1用蓝色表示,浪费的地址为白色,i为红色,c2为绿色
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
int t = sizeof(struct S1);
printf("%d", t);
}
解决这个问题后,大家结构体内存对齐问题就没有问题啦,大家快快算算看🙌
//练习2-结构体嵌套问题
struct S3
{
?? ?double d;
?? ?char c;
?? ?int i;
};
struct S4
{
?? ?char c1;
?? ?struct S3 s3;
?? ?double d;
};
printf("%d\n", sizeof(struct S4));
解释如下:c1跳过,既然要计算s3,那么我们就要返回S3中,先算s3的内存占多大,d为double类型8个字节min(8,8),0是8的整数倍,c随便,i对齐数为min(4,8),9不是4的倍数,故跳到12,最后算下来s3占的字节数为16图解如下。
计算完s3,我们终于可以计算s4了,s3的填入看看我们对齐规则的第四点:4. 如果嵌套了结构体的情况,嵌套的结构体成员对?到??的成员中最?对?数的整数倍处,结构体的整体??就是所有最?对?数(含嵌套结构体中成员的对?数)的整数倍。在本题结构体成员的成员中最大对齐数为d的8,故s3的对齐数为8,d为min(8,8),最后别忘了,第三点:3. 结构体总??为最?对?数(结构体中每个成员变量都有?个对?数,所有对齐数中最?的)的整数倍。
总图解如下:(c1蓝色,s3绿色,d红色,白色为浪费空间)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
//练习4-结构体嵌套问题
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
return 0;
}
补充一个小知识:编译器的默认对齐数是可以自己修改的,c语言就是这么霸道
#pragma这个预处理指令,可以修改编译器的默认对齐数。用法如下:要加一个pack()
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#pragma pack(1)//设置默认对?数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对?数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}
到这我就已经把结构体的对齐给你讲清楚了(大家一定要自己算算),考试遇到直接给它喵了!!!
关于结构体为了防止大家出错,这里我给大家总结出来两个易错点供大家参考,大家看看自己有没有遇到这些情况,遇到了又该如何解决呢?下面我将带大家避免掉入这些坑。
大家先看看下面这个代码合不合法
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], * p;
//下面这个合法吗
*p = x;
我们可以看到这是因为没有给结构体命名导致的争议,其实这是非法的,因为这两个结构体是不同的🙅?,也就是说这两个结构体不是同一个类型的,所以匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。
下面这个代码合法吗?🐱?🏍
如果正确的话那么sizeof(struct Node)的值是多少?
很明显这是非法的,因为这样sizeof(struct Node)的值是无穷的,就好像递归没有出口一样
正确引用如下:
我们可以看到这就是类似链表洛
struct Node
{
?? ?int data;
?? ?struct Node* next;
};
定义结构体不要使用匿名结构体了(好习惯要记住哟!!!)
其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。