【C语言】结构体&&内存对齐问题

【C语言】结构体&&内存对齐问题,第1张

✨ 写在前面

🎃 哈喽大家好👋👋👋
🌱 作为一个初入编程的大学生,知识浅薄,但还是要学习大佬写一下前言滴(🤭)
🌱 我的其他文章
       1.【C语言】字符串函数使用+模拟【上】
       2.【C语言】彻底搞明白C语言一大关卡—C指针【初阶】
       3.【C语言】字符串 *** 作函数&&字符串查找&&内存 *** 作函数【下】

🌱 初入编程的世界 前方"路漫漫"🛣️ 每天我们都要进步一点点💧
🌱 希望分享知识的同时可以和你们一起进步🍻

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

📇文章目录
  • 🏷️ 结构体
    • 📜1.什么是结构体
    • 📜2.结构体类型的声明
      • ⭐特殊的声明
    • 📜3.结构体的自引用
    • 📜4.结构体变量的定义和初始化
    • 📜5.结构体的内存对齐⭐
      • ⭐具体的对齐规则是什么呢?
      • 🏷️offsetof--宏
      • ⭐为什么存在对齐呢?
    • 📜6.修改默认对齐数
    • 📜7.结构体传参

🏷️ 结构体 📜1.什么是结构体

之前我们 *** 作的都是数字,在计算机中我们如何描述一个复杂对象呢
比如描述一个学生:
1.学号
2.姓名
3.年龄
4.成绩
都需要我们进行定义
因此出现了结构体-----用于描述复杂对象

📜2.结构体类型的声明
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.结构体变量的定义和初始化

下面是常见的结构体变量的定义方式和其初始化

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 };//结构体嵌套初始化

📜5.结构体的内存对齐⭐

嗨害嗨~ 好玩的来咯~

我们先来看两个例子


我们换一下顺序

嗯嗯嗯? 这怎么换一下顺序就不一样了呢???
这是因为结构体在内存中是存在某种对齐的
使得原来的空间因为对齐而变大了


⭐具体的对齐规则是什么呢?
  1. 第一个成员在与结构体变量偏移量为0的地址处。


  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
    VS中默认的值为8
    Linux没有默认对齐数 自身大小就是默认对齐数
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

偏移量如下图所示

现在我们对上面的 结构体S1为什么是 12进行解释

相信结构体S2你能知道为什么是8了吧
再举嵌套结构体的例子

🏷️offsetof–宏

offsetof用来计算结构体成员相对于起始位置的偏移量
头文件:stddef.h
使用:offsetof(struct tag,member)

以上面的嵌套结构体为例:


⭐为什么存在对齐呢?
  1. 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

比如某个平台只能在4的倍数的地址处进行数据读取,那么这时候就需要吧数据对齐到地址为4的倍数的地方 否则无法读取内存

  1. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。



    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
    问。


举个例子:
对于

struct S
{
	char c;
	int i;
};
struct S s

在32位机器上–一次读写4个字节
如果不考虑对齐:


如果你想拿出其中的i
你需要读取两次
>

总的来说

结构体的内存对齐是拿空间来换取时间的做法

在设计结构体的时候-要既要满足对齐,又要节省空间:

让占用空间小的成员尽量集中在一起

📜6.修改默认对齐数


可以看到 不同默认对齐数情况下定义的结构体 大小是不一样的!
需要注意的是 默认对齐数 至少为1
那既然这样为什么不知直接改成1呢?----改成一就不对齐了
我们一般改对齐数为2的几次方 以匹配机器的读取习惯

定义S1的时候为什么需要恢复 对齐数呢?
如果未对#pragma pack进行取消,那么其作用域便是本文件,如果是头文件,则会影响到包含它的模块,如此一来,由于没有及时地设回去,可能会莫名其妙地引起程序崩溃 所以一定要设回去

📜7.结构体传参

显然 我们直接可以利用形参,把结构体拷贝一份传入函数
但是我们知道 结构体的大小一般是比较大的,如果结构体过大,这样会在栈空间开辟一大块用于存放形参的空间,显然造成了内存的浪费
如:


这样太浪费空间了
所以我们一般不这样传递
一般我们直接传递结构体的指针
利用指针访问结构体成员




可以看到 两种结果是一样的
总结:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。



如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
所以! 结构体传参的时候,要传结构体的地址!!!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/567261.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存