加入收藏 | 设为首页 | 会员中心 | 我要投稿 威海站长网 (https://www.0631zz.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

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

发布时间:2022-10-17 14:01:48 所属栏目:Unix 来源:互联网
导读: 在声明结构的时候,可以不完全声明。如:

则此时创建变量就不能像上面所示的写法创建了

那么应该怎么创建变量呢?如图

该写法称为匿名结构体类型。意思就是标签名字省略不写,但是你想用

在声明结构的时候,可以不完全声明。如:

在这里插入图片描述

则此时创建变量就不能像上面所示的写法创建了

在这里插入图片描述

那么应该怎么创建变量呢?如图

在这里插入图片描述

该写法称为匿名结构体类型。意思就是标签名字省略不写,但是你想用这个类型创建变量,只能把变量写在全局变量的位置,否则不能创建变量,这个匿名结构体类型只能使用一次。

那如果在两个成员一模一样的情况下,写一个匿名结构体类型,再写一个匿名结构体的指针,那么这两个结构体在编译器看来是不是一样的?

在这里插入图片描述

我们在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的倍数UNIX 特殊变量,后面没有浪费空间,得出最后结构体大小为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语言自定义类型-结构体的详解了,希望该文章对大家的学习有所帮助!

制作不易,点个关注点个赞吧!

(编辑:威海站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!