✨ 写在前面
🎃 哈喽大家好👋👋👋
🌱 作为一个初入编程的大学生,知识浅薄,但还是要学习大佬写一下前言滴(🤭)
🌱 我的其他文章
1.【C语言】字符串函数使用+模拟【上】
2.【C语言】彻底搞明白C语言一大关卡—C指针【初阶】
3.【C语言】字符串 *** 作函数&&字符串查找&&内存 *** 作函数【下】🌱 初入编程的世界 前方"路漫漫"🛣️ 每天我们都要进步一点点💧
🌱 希望分享知识的同时可以和你们一起进步🍻
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
📇文章目录🏷️ 结构体 📜1.什么是结构体
- 🏷️ 结构体
- 📜1.什么是结构体
- 📜2.结构体类型的声明
- ⭐特殊的声明
- 📜3.结构体的自引用
- 📜4.结构体变量的定义和初始化
- 📜5.结构体的内存对齐⭐
- ⭐具体的对齐规则是什么呢?
- 🏷️offsetof--宏
- ⭐为什么存在对齐呢?
- 📜6.修改默认对齐数
- 📜7.结构体传参
之前我们 *** 作的都是数字,在计算机中我们如何描述一个复杂对象呢
📜2.结构体类型的声明
比如描述一个学生:
1.学号
2.姓名
3.年龄
4.成绩
都需要我们进行定义
因此出现了结构体-----用于描述复杂对象struct tag{ member-list;//成员列表 }variable-list;//用结构体创建的变量列表
以描述一个学生为例:
struct Stu{ char name[20];//学生姓名 int age;//学生年龄 char sex[5];//学生性别 char id[20];//学生学号 };//分号不可丢掉!
这就声明了一个结构体类型 Stu
⭐特殊的声明在结构体声明中 可以不完全声明:省略结构体标签 tag------匿名结构体类型
struct //这里省略结构体标签 { int a; char c; double c; }x; struct { int a; char c; double c; }a[20],*p;
这时候其实就会出现一些问题
虽然两个匿名结构体的成员一模一样
但是编译器会认为 上面定义的两个结构体不是同一类型的
所以p=&x;
这条语句是不合法的!!
📜3.结构体的自引用
其实,匿名结构体 只能在定义的时候这里使用一次
之后就不可以使用了(你连个名字都没有,怎么创建新的变量呢?
所以 这东西根本没什么用顾名思义 结构体自己引用自己
struct Node { int data; struct Node Next; };
这样可以吗?
一个结构体中包含一个int 数据和 一个同类型的结构体
但是这样就会有一个问题:
结构体的大小是 int + 内部结构体的大小
而内部结构体的大小又是它内部int和内部的结构体大小的和
这就成死递归了
所以我们不这样自引用
正确的自引用方式是:
struct Node { int data; struct Node* next;//里面是同类型结构体的地址 };
这里可以使用typedef–类型重定义进行定义
typedef struct Node { int data; struct Node* next; }Node;
即–把struct Node重新定义成了 Node
📜4.结构体变量的定义和初始化下面是常见的结构体变量的定义方式和其初始化
📜5.结构体的内存对齐⭐struct Point { int x; int y; }p1; //声明类型的同时定义变量 p1 struct Point p2;//定义结构体变量p2 //初始化:定义变量的同时赋初值。
struct Point p3 = { x, y }; struct Stu //类型声明 { char name[15];//名字 int age; //年龄 }; struct Stu s = { "zhangsan", 20 };//初始化 struct Node { int data; struct Point p; struct Node* next; }n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化 struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化
嗨害嗨~ 好玩的来咯~
我们先来看两个例子
我们换一下顺序
嗯嗯嗯? 这怎么换一下顺序就不一样了呢???
这是因为结构体在内存中是存在某种对齐的
使得原来的空间因为对齐而变大了
⭐具体的对齐规则是什么呢?
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
◽VS中默认的值为8
◽Linux没有默认对齐数 自身大小就是默认对齐数- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
偏移量如下图所示
🏷️offsetof–宏
现在我们对上面的 结构体S1为什么是 12进行解释
相信结构体S2你能知道为什么是8了吧
再举嵌套结构体的例子
offsetof用来计算结构体成员相对于起始位置的偏移量
头文件:stddef.h
使用:offsetof(struct tag,member)以上面的嵌套结构体为例:
⭐为什么存在对齐呢?
- 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常比如某个平台只能在4的倍数的地址处进行数据读取,那么这时候就需要吧数据对齐到地址为4的倍数的地方 否则无法读取内存
- 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
举个例子:
对于struct S { char c; int i; }; struct S s
在32位机器上–一次读写4个字节
如果不考虑对齐:
如果你想拿出其中的i
你需要读取两次
>
总的来说
结构体的内存对齐是拿空间来换取时间的做法
在设计结构体的时候-要既要满足对齐,又要节省空间:
让占用空间小的成员尽量集中在一起
📜6.修改默认对齐数
可以看到 不同默认对齐数情况下定义的结构体 大小是不一样的!
需要注意的是 默认对齐数 至少为1
那既然这样为什么不知直接改成1呢?----改成一就不对齐了
我们一般改对齐数为2的几次方 以匹配机器的读取习惯📜7.结构体传参定义S1的时候为什么需要恢复 对齐数呢?
如果未对#pragma pack进行取消,那么其作用域便是本文件,如果是头文件,则会影响到包含它的模块,如此一来,由于没有及时地设回去,可能会莫名其妙地引起程序崩溃 所以一定要设回去显然 我们直接可以利用形参,把结构体拷贝一份传入函数
但是我们知道 结构体的大小一般是比较大的,如果结构体过大,这样会在栈空间开辟一大块用于存放形参的空间,显然造成了内存的浪费
如:
这样太浪费空间了
所以我们一般不这样传递
一般我们直接传递结构体的指针
利用指针访问结构体成员
可以看到 两种结果是一样的
总结:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
所以! 结构体传参的时候,要传结构体的地址!!!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)