0


C语言-自定义类型-结构体(详解结构体内存对齐)

文章目录


📚 前言

本文会详解C语言中自定义类型中结构体的使用、声明、自引用、结构体变量的定义和初始化、*结构体内存对齐、结构体传参等详细说明


📘 一、结构的声明

📖结构体的基础知识

结构是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量

📖1.结构体的声明

在这里插入图片描述
上图所示,

MyStruct

是属于结构体标签,根据个人实际需求来自行更改;大括号里面的称为成员列表(成员变量),里面可以有一个也可以有多个,每个成员可以为不同类型的变量;
有了结构体类型,接着就要创建结构体变量了,如下
在这里插入图片描述


📘 二、特殊的声明

在声明结构的时候,可以不完全声明。如:
在这里插入图片描述
则此时创建变量就不能像上面所示的写法创建了
在这里插入图片描述
那么应该怎么创建变量呢?如图
在这里插入图片描述
该写法称为匿名结构体类型。意思就是标签名字省略不写,但是你想用这个类型创建变量,只能把变量写在全局变量的位置,否则不能创建变量,这个匿名结构体类型只能使用一次。
那如果在两个成员一模一样的情况下,写一个匿名结构体类型,再写一个匿名结构体的指针,那么这两个结构体在编译器看来是不是一样的?
在这里插入图片描述
我们在main函数中测试一下,将s1的地址赋值给p变量,看看能否正常赋值
在这里插入图片描述
在这里插入图片描述
可以发现编译器给出了警告,从“ * ”到“ * ”类型不兼容,也就是说编译器认为两边的类型不相同的,所以在匿名结构体类型里面虽然成员是一样的,但是编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的!


📘 三、结构自引用

在结构中包含一个类型为该结构体本身的成员是否可以呢?
在这里插入图片描述
如果像这样去设计,那么

sizeof(struct Node)

有多大,

int

类型是4个字节,那么n是多大呢?那么这个结点里还有一个data,还有一个n,就会一直循环下去,所以没办法确定,所以该写法是错误的。
当一个结点要找一个同类型的下一个结点的时候,叫做自引用在这里插入图片描述
如上图链表所示,一个结点既要保存1这个数,又要有能力找到下一个结点,所以我们可以认为这一个结点就相当于一个复杂对象,我们把它设计成一个结构体。
每一个结点都会存上下一个结点的地址,所以每个结点我们可以分为两个部分,一部分存数据,一部分存地址
在这里插入图片描述

📖 解决方案 1:

使用指针。由于指针的长度是确定的(在32位及其上指针长度为4)所以编译器能够确定该结构体的长度

structNode{int data;structNode* next;};
struct Node* next

在结构体内找到同类型的下一个结点的地址,就是结构体的自引用。使用该结构体,结构体的类型就是

struct Node

📖 解决方案 2:

使用

typedef
typedefstructNode{int data;structNode* next;}Node;

该方案的目的是使用

typedef

为结构体创建一个别名。


📘 四、结构体变量的定义和初始化

变量的创建:
在这里插入图片描述
变量的初始化:
创建的同时进行初始化
在这里插入图片描述
在这里插入图片描述


📕 五、*结构体内存对齐

我们先来测试一下,将两个结构体内部顺序变换一下,问这两个结构体的大小是多少?是相等吗?还是?
在这里插入图片描述
程序运行得出结果:
在这里插入图片描述
为什么这个结果变成12和8呢?这就涉及到结构体的内存对齐了,意思就是,结构体在内存中要进行一定的对齐,放到对齐的位置上才可以。
在了解内存对齐之前,我们首先来了解一下

offsetof

宏,用来计算结构体成员相对于起始位置的偏移量
在这里插入图片描述
则由结构体成员的偏移量可以得出,S1在内存中是如何存放的,如下图
在这里插入图片描述
接着S2也是同理,只是位置顺序相对于S1变换了,所以内存空间如下图
在这里插入图片描述
在这里插入图片描述
可以看出,使用

offsetof

测试出来变量a、b、i,的起始位置的偏移量与上述S1,S2的内存空间图中,a、b、i的起始位置是一样的,一个是0,4,8,一个是0,1,4。
所以结构体内存到底是如何对齐的?

结构体内存对齐的规则:

  1. 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处。
  2. 从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处。(对齐数:结构体成员自身大小和默认对齐数的较小值。VS编译器环境:8;Linux编译环境默认不设对齐数,对齐数就是结构体成员自身大小)
  3. 结构体的总大小,必须是最大对齐数的整数倍。 每个结构体成员都有一个对齐数,其中最大的对齐数就是最大对齐数。
  4. 如果仙桃了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

满足了以上结构体内存对齐的规则,我们可以分析出,上述内存图为何这么设计。如S1为例子:
在这里插入图片描述
在这里插入图片描述
接着来探讨S2,s2中变量a本身为1个字节,从起始位置0开始,变量b自身大小为1默认对齐数为8,所以对齐数为1,b就要放在1的倍数空间上,所以直接放在空间为1的位置处,接着找i,i的自身大小为4默认对齐数为8,所以对齐数为4,所以要放在4的倍数的空间上,所以2的偏移和3的偏移用不上,所以从4开始;最后所有对齐数最大的是4,所以结构体大小就是4的倍数,而8刚好是4的倍数,后面没有浪费空间,得出最后结构体大小为8
在这里插入图片描述
结构体内存对齐规则的第四条规则举实例说明:

structS3{double d;char c;int i;};structS4{char a;structS3 s3;double d;};intmain(){printf("%d\n",sizeof(structS4));return0;}

在这里插入图片描述
由图所示,所有对齐数的里面最大的是8,32是8的倍数,不需要往后浪费空间,所以s4的大小为32。
那么为什么存在内存对齐?

  1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台智能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。总的来说: 结构体的内存对齐是拿空间来换取时间的做法。

那么在设计结构体时,既要满足内存对齐,又要节省空间,如何做到呢?

struct S2

为例,让占用空间小的成员尽量集中在一起即可满足!


📘 六、修改默认对齐数

使用

#pragma

这个预处理指令,来改变默认对齐数

// 设置默认对齐数#pragmapack(1)structS1{char a;int i;char a2;};// 恢复默认对齐数#pragmapack()intmain(){printf("%d\n",sizeof(structS1));return0;}

运行结果如下:
在这里插入图片描述
在这里插入图片描述
可以看出,将默认对齐数手动设置成1,则在内存中可以看出变量不需要对齐了,直接往后依次开辟空间。


📝总结

以上就是对C语言自定义类型-结构体的详解了,希望该文章对大家的学习有所帮助!
制作不易,点个关注点个赞👍吧!


本文转载自: https://blog.csdn.net/m0_56742126/article/details/127180945
版权归原作者 it_NunU 所有, 如有侵权,请联系我们删除。

“C语言-自定义类型-结构体(详解结构体内存对齐)”的评论:

还没有评论