C语言进阶【7】--结构体-位段【2】(位段的概念你不想了解吗?)

结构体内存对齐

  • 计算结构体的大小:我们都知道数据类型都有大小,不同的数据类型占有的字节不一样。比如,char占有1个字节,int占有4个字节……。结构体也是一种数据类型,也有大小。我们先来举个计算结构体大小的例子,进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%zd\n",sizeof(struct S1));
	return 0;
}

大家可以先猜一下这个结构体的大小。我们先来猜测一下:成员变量有 char ,intchar 。char占有1个字节,int占有4个字节,那么这个结构体的大小为 1+4+1=6个字节。答案是不是这个呢?我们来运行一下这个结果吧。在这里插入图片描述
运行结果与我们的猜测大相径庭,这是为什么呢?这就要掌握结构体的内存对齐规则了。

  • 内存对齐规则:如下所示:
    • 1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
    • 2 .其它成员变量要对齐到某个数字(对齐数)的整数倍的地址处
      对齐数=编译器默认的一个对齐数与该成员变量大小的较小值
      • VS中默认的值为8.
      • Linux中gcc没有默认对齐数,对齐数就是成员自身的大小
    • 3 .结构体总体的大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍
    • 4 .如果有嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
  • 进行讲解: 我们来讲解上面代码的结果为什么是12个字节。首先来看结构体内存对齐规则【1】,遇到结构体时,内存会先创建个预处理空间不够时再增加,多余时就去掉多余的部分)。然后,我们把第一个成员变量(char c1)放在离起始地址偏移量(偏移量:就是与起始地址相差的字节数)为0的地方。如图所示:在这里插入图片描述
    我们第一个成员的位置已经放好了,接着放第二个成员(第三个成员类推)。看内存对齐规则的第2个规则,我们根据这个规则进行分析:
类型       占有字节数      默认对齐数      对齐数
int i         4               8             4
char c2       1               8             1
我们要找到偏移量为4(对齐数)的最小整数倍,然后再把 int i给放进去,char c2就紧跟其后,
因为无论偏移量为多少都是1(对齐数)的倍数。

如图所示:在这里插入图片描述
我们按照前面的两个规则,已经把结构体里面的成员变量给安排好了。我们数一下后,发现占有的空间为9个,但是我们输出结果是12,还是不一样。这就要用到内存对齐规则3。我们可以数一下,共占有9个字节(从char c1开始到char c2结束,包括之间的空白字节),9个字节不是最大对齐数(4)的整数倍,我们要再增加3个字节到12个字节,刚好是4的整数倍。所以,最后结构体的大小为12个字节。如图所示:在这里插入图片描述

  • 计算含结构体的结构体大小
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S4
{
	char c1;
	struct S1 s3;
	double d;
};
int main()
{
	printf("%zd\n",sizeof(struct S4));
	return 0;
}

大家可以猜测一下这个结构体的大小。我们进行结果图展示:在这里插入图片描述
我们在struct S4 包含了 struct S1结构体类型,我们在上面已经计算过了struct S1的大小——12个字节。我们知道,char,double分别占1个字节和8个字节,struct S1占有12个字节,如果我们抛开内存对齐规则,估计也就是21个字节左右。但是,答案是24个字节。这就需要用到对齐规则4了。我们知道 struct S1的最大对齐数是4。所以,struct S1的偏移量要为自己最大对齐数(4)的整数倍。关于double和char对齐就不讲了,类比前面就OK了。进行如图所示:在这里插入图片描述

  • 结构体的弊端:不知道大家有没有发现,我们根据内存对齐规则所画的内存结构图,之间有很多的空白字节,而这些空白字节我们什么数据也没存,就那样空着了,这就是结构体的弊端——浪费内存空间而这些浪费的空间,我们是无法使用的,就那样空着了。我们接下来讲的结构体的位段就很节省空间
  • 修改内存对齐数:在VS中默认对齐数为8,如果我们感觉到不合理的话,我们还可以修改这个默认的对齐数。【注意:我们修改后要再修改回来,以免影响我们后面在使用(万一我们还想使用默认呢)】。我们就以开头的代码进行展示(做个对比):
#pragma pack(数字)   //修改默认对齐数的代码
·····
·····
#pragma pack()    //恢复默认对齐数值得代码【括号里面。啥也不填】
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#pragma pack(1)      //默认对齐数修改为 1
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()   //恢复默认对齐数
int main()
{
	printf("%zd\n", sizeof(struct S1));
	return 0;
}

结果运行图:在这里插入图片描述

  • 为什么会有内存对齐数?:我们在一个内存里面直接放上数据不就OK了吗?那样做还可能比内存对齐要省空间些。之所以这样做有两个原因
    • 1.平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些 硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常。
    • 2.性能原因:数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
    • 3总结:结构体就是拿空间来换取时间得效率

结构体传参

咱们见过数组传参,函数传参和普通数值传参等。今天讲的结构体传参也没什么新奇的,就是创建个结构体类型数据,然后传过去就OK了。我们来讲两个传参方式:

  • 传值传参:进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct S1
{
	char c1;
	int i;
	char arr[20];
};
void my_printf(struct S1 s)
{
	printf("%c %d %s\n",s.c1,s.i,s.arr);
}
int main()
{
	struct S1 c1 = {'w',100,"zhangsan"};
	my_printf(c1);
	return 0;
}

结果运行图:在这里插入图片描述

  • 传址传参:进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct S1
{
	char c1;
	int i;
	char arr[20];
};
void my_printf(struct S1* s)
{
	printf("%c %d %s\n",s->c1,s->i,s->arr);
}
int main()
{
	struct S1 c1 = {'w',100,"zhangsan"};
	struct S1* P = &c1;
	my_printf(P);
	return 0;
}

结果运行图:在这里插入图片描述
结构体传参也没什么特别的地方,正常用就OK。

结构体实现位段

前面,咱们提到过,位段可以大大节省内存空间。我们从它的名字——位段。就能猜个大概,猜测它与位有关。答案就是如此.我们先看看这位段长啥样,进行代码展示:

struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};

是不是和结构体长得很像,只是成员变量差别挺大的。

  • 位段的解读
    • 1 .位段的声明,定义变量和结构体类似,就是成员变量不同
    • 2.位段的成员变量只能是 int ,unsigned int和char类型(在C99之前)。C99中,位段成员可是其它类型
    • 3.位段的成员格式:数据类型+变量+冒号+数字+分号
    • 4.变量后面的数字就是表示,你想给数据申请多少位。多了更好,不够就发生数据截断
  • 位段的大小计算:进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct s
{
	char i : 3;
	char k : 5;
	char j : 8;
};
int main()
{
	printf("%zd\n",sizeof(struct s));
	return 0;
}

结果运行图:在这里插入图片描述

  • 位段申请空间编辑器会先给第一个成员变量分配好空间。我们第一个成员申请了3位,现在有个问题 ,这3位是从右边排呢?还是从左边排呢?如图所示:在这里插入图片描述
    对于从左向右排,还是从右向左排。C语言中未定义(未规定),具体的排法由编译器厂商决定了。VS中是从右向左排的。当编译器为第一个成员分配耗空间后,我们也申请了3个位,第一个字节还剩5个位。这个时候,char k申请5个位,它俩刚好凑齐一个字节。这个时候 ,到char j 申请了8个位,也刚好一个字节。所以,它们总共占有2个字节。
  • 注意事项
    • 1.位段的里面的成员一般都是相同类型的数据,不同类型很少见。因为位段本身不稳定,两个不同的数据可能会占用同一个字节,这就导致不稳定性,读取数据就可能出错
    • 2.位段不可以用指针访问。因为我们只按字节给的单位(硬件就是那么设计的),没有给每个位进行编号,所有,无法通过地址去访问位里面的数据
  • 位段的使用
    • 1.我们可以直接赋值,进行代码展示:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct s
{
	char i : 3;
	char k : 5;
	char j : 8;
}s1;
int main()
{
	s1.i = 10;
	s1.k = 12;
	s1.j = 13;
	printf("%d %d %d\n",s1.i,s1.k,s1.j);
	return 0;
}

结果运行图:在这里插入图片描述
我们发现,输出的结果与我们输入的数值有些不符合,这就与我们申请多少位有关了!!!。如图所示:在这里插入图片描述

  • 为我们还可以输入赋值:进行代码展示:
这样的代码是错的:
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct s
{
	char i : 3;
	char k : 5;
	char j : 8;
};
int main()
{
	struct s s1 = {0};
	scanf("%d",&(s1.i));         //错误的输入方式,不可以取地址
	return 0;
}

因为咱们讲过了,位(它没有地址)是不可以通过指针(地址)访问的。

正确的访问方式:

#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
struct s
{
	char i : 3;
	char k : 5;
	char j : 8;
};
int main()
{
	struct s s1 = {0};
	int b = 0;
	scanf("%d",&b);
	s1.k = b;
	printf("%d\n",s1.k);
	return 0;
}

结果运行图:在这里插入图片描述
我们需要创建个中间值变量,通过这个中间变量进行赋值

  • 位段的优点节省内存空间

    • 位段的应用:基于它比较节省内存空间,所以基于它这个优点,我们应用于网络协议中。如图所示的网络协议图:在这里插入图片描述

    我们知道现在是信息很发达的时代,我们每天都在从网络上获取我们想要的数据,我们想要的数据或者发送的数据,都要经过这个网络协议。这个网络协议就像快递一样,把我们要发送或接收的数据进行打包,形成了数据包,放在网络上。既然放在网络上,那么数据包就要小,要不然网络就贼卡。因为,位段很省空间,所以就会按照位段形式进行数据打包。

  • 位段的缺点

    • 1.位段不能跨平台使用,因为它申请位的顺序不确定,而且对于多余的空间是舍弃,还是保留,也没定义。不同的编译器处理的方式不同,所以不能跨平台
    • 2.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题

彩蛋时刻!!!

https://www.bilibili.com/video/BV13P411n7DL/?spm_id_from=333.337.search-card.all.click&vd_source=7d0d6d43e38f977d947fffdf92c1dfad在这里插入图片描述
每章一句生活不是为了赶路,而是为了感受路感谢你能看到这里,点赞+关注+收藏+转发是对我最大的鼓励,咱们下期见!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值