文章目录
📚 前言
本文会详解C语言中自定义类型中结构体的使用、声明、自引用、结构体变量的定义和初始化、*结构体内存对齐、结构体传参等详细说明
📘 一、结构的声明
📖结构体的基础知识
结构是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量
📖1.结构体的声明
上图所示,MyStruct
是属于结构体标签,根据个人实际需求来自行更改;大括号里面的称为成员列表(成员变量),里面可以有一个也可以有多个,每个成员可以为不同类型的变量;
有了结构体类型,接着就要创建结构体变量了,如下
📘 二、特殊的声明
在声明结构的时候,可以不完全声明。如:
则此时创建变量就不能像上面所示的写法创建了
那么应该怎么创建变量呢?如图
该写法称为匿名结构体类型。意思就是标签名字省略不写,但是你想用这个类型创建变量,只能把变量写在全局变量的位置,否则不能创建变量,这个匿名结构体类型只能使用一次。
那如果在两个成员一模一样的情况下,写一个匿名结构体类型,再写一个匿名结构体的指针,那么这两个结构体在编译器看来是不是一样的?
我们在main函数中测试一下,将s1的地址赋值给p变量,看看能否正常赋值
可以发现编译器给出了警告,从“ * ”到“ * ”类型不兼容,也就是说编译器认为两边的类型不相同的,所以在匿名结构体类型里面虽然成员是一样的,但是编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的!
📘 三、结构自引用
在结构中包含一个类型为该结构体本身的成员是否可以呢?
如果像这样去设计,那么sizeof(struct Node)
有多大,int
类型是4个字节,那么n是多大呢?那么这个结点里还有一个data,还有一个n,就会一直循环下去,所以没办法确定,所以该写法是错误的。
当一个结点要找一个同类型的下一个结点的时候,叫做自引用。
如上图链表所示,一个结点既要保存1这个数,又要有能力找到下一个结点,所以我们可以认为这一个结点就相当于一个复杂对象,我们把它设计成一个结构体。
每一个结点都会存上下一个结点的地址,所以每个结点我们可以分为两个部分,一部分存数据,一部分存地址
📖 解决方案 1:
使用指针。由于指针的长度是确定的(在32位及其上指针长度为4)所以编译器能够确定该结构体的长度
struct Node
{
int data;
struct Node* next;
};
struct Node* next
在结构体内找到同类型的下一个结点的地址,就是结构体的自引用。使用该结构体,结构体的类型就是struct Node
。
📖 解决方案 2:
使用typedef
typedef struct Node
{
int data;
struct Node* next;
}Node;
该方案的目的是使用typedef
为结构体创建一个别名。
📘 四、结构体变量的定义和初始化
变量的创建:
变量的初始化:
创建的同时进行初始化
📕 五、*结构体内存对齐
我们先来测试一下,将两个结构体内部顺序变换一下,问这两个结构体的大小是多少?是相等吗?还是?
程序运行得出结果:
为什么这个结果变成12和8呢?这就涉及到结构体的内存对齐了,意思就是,结构体在内存中要进行一定的对齐,放到对齐的位置上才可以。
在了解内存对齐之前,我们首先来了解一下offsetof
宏,用来计算结构体成员相对于起始位置的偏移量
则由结构体成员的偏移量可以得出,S1在内存中是如何存放的,如下图
接着S2也是同理,只是位置顺序相对于S1变换了,所以内存空间如下图
可以看出,使用offsetof
测试出来变量a、b、i,的起始位置的偏移量与上述S1,S2的内存空间图中,a、b、i的起始位置是一样的,一个是0,4,8,一个是0,1,4。
所以结构体内存到底是如何对齐的?
结构体内存对齐的规则:
- 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处。
- 从第二个成员开始,要对齐到某个【*对齐数】的整数倍的偏移处。(*对齐数:结构体成员自身大小和默认对齐数的较小值。VS编译器环境:8;Linux编译环境默认不设对齐数,对齐数就是结构体成员自身大小)
- 结构体的总大小,必须是最大对齐数的整数倍。
每个结构体成员都有一个对齐数,其中最大的对齐数就是最大对齐数。- 如果仙桃了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
满足了以上结构体内存对齐的规则,我们可以分析出,上述内存图为何这么设计。如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
结构体内存对齐规则的第四条规则举实例说明:
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char a;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
return 0;
}
由图所示,所有对齐数的里面最大的是8,32是8的倍数,不需要往后浪费空间,所以s4的大小为32。
那么为什么存在内存对齐?
- 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台智能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。- 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总的来说:
结构体的内存对齐是拿空间来换取时间的做法。
那么在设计结构体时,既要满足内存对齐,又要节省空间,如何做到呢?
由struct S2
为例,让占用空间小的成员尽量集中在一起即可满足!
📘 六、修改默认对齐数
使用#pragma
这个预处理指令,来改变默认对齐数
// 设置默认对齐数
#pragma pack(1)
struct S1
{
char a;
int i;
char a2;
};
// 恢复默认对齐数
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
运行结果如下:
可以看出,将默认对齐数手动设置成1,则在内存中可以看出变量不需要对齐了,直接往后依次开辟空间。
📝总结
以上就是对C语言自定义类型-结构体的详解了,希望该文章对大家的学习有所帮助!
制作不易,点个关注点个赞👍吧!