笔记21-4(C语言进阶 结构体练习)

目录

注:

结构体内存对齐

题目一:求两个结构体类型所占内存的大小。

分析(以A为例子):

参考结果:

宏和结构体(位段)的定义、内存对齐

题目二:求由malloc函数开辟的空间大小。

分析:

验证结果:

结构体(位段)、结构体指针

题目三:求出程序允许结果

分析:

数据存储部分

最后的打印环节

联合体的大小计算

题目四:计算一个联合体所占内存空间的大小。

分析:

程序执行的结果:

大小端的存储问题

题目五:求出程序的执行结果。

分析:


注:

        本笔记参考B站up:鹏哥C语言的视频


结构体内存对齐

题目一:求两个结构体类型所占内存的大小。

情景:在32位系统环境下,编译选项位4字节对齐。

问:sizeof(A) 和 sizeof(B) 分别是多少?

struct A
{
	int a;
	short b;
	int c;
	char d;
};

struct B
{
	int a;
	short b;
	char c;
	int d
};

参考笔记21-1

分析(以A为例子):

struct A
{
	int a;
	short b;	//大小是2,小于4,所以默认对齐数是2(取小)
	int c;		//大小是4
	char d;		//大小是1,小于4,默认对齐数是1
};

通过上图可以得知,sizeof(A) 所能得出的结果应该是:16

参考结果:

#pragma pack(4)
int main()
{
	printf("%d\n", (int)sizeof(struct A));
	printf("%d\n", (int)sizeof(struct B));
	return 0;
}
#pragma pack()

宏和结构体(位段)的定义、内存对齐

题目二:求由malloc函数开辟的空间大小。

情景:A = 2, B = 3。

问:pointer被分配到多少字节的空间?

#include<stdlib.h>

#define MAX_SIZE A+B

struct _Record_Struct
{
	unsigned char Env_Alarm_ID : 4;
	unsigned char Paral : 2;
	unsigned char state;
	unsigned char avail : 1;
}*Env_Alarm_Record;

int main()
{
	struct _Record_Struct* pointer = 
    (struct _Record_Struct*)malloc(sizeof(struct _Record_Struct) * MAX_SIZE);
	return 0;
}

分析:

首先,MAX_SIZE被替换成:2 + 3(注意计算顺序

(struct _Record_Struct*)malloc(sizeof(struct _Record_Struct) * 2 + 3);

对于结构体_Record_Struct,首先研究每个成员到底开辟了多少空间。(位段的知识点参考:笔记21-1

首先看第一个成员Env_Alarm_ID:

unsigned char Env_Alarm_ID : 4;

一个 unsigned char 类型的成员会开辟 1个字节8bit 大小的空间,而 Env_Alarm_ID 这个成员需要 4bit 大小的空间,此时还剩下 4bit 的空间没被使用。

---

接下来看第二个成员Paral:

unsigned char Paral : 2;

这个成员只需要使用 2bit 的空间,而上个成员还有开辟而没有使用的 4bit 大小的空间,所以成员 Paral 接下来将会使用这 4bit 的空间,此时还剩下 2bit 的空间没有被使用。

---

再看第三个成员state:

unsigned char state;

注意:这个成员并不是位段成员。

成员state将会开辟 1个字节 的空间,并且这个字节的空间都将被state所使用。

---

最后看第四个成员avail:

unsigned char avail : 1;

这个位段成员所需要的空间需要被另外开辟,所以又开辟了 1个字节 的空间,但是该成员只会使用 1bit 的空间,将有 7bit 的空间不会被使用。

综上所述,这个结构体类型的大小就是 3个字节 。

所以 sizeof(struct _Record_Struct) * MAX_SIZE) = 3 * 2 + 3 = 9

验证结果:

手动把 A 和 B 先进行定义。

int main()
{
	struct _Record_Struct* pointer = 
		(struct _Record_Struct*)malloc(sizeof(struct _Record_Struct) * MAX_SIZE);
	printf("%d\n", sizeof(*pointer));
	return 0;
}

打印结果:

结构体(位段)、结构体指针

题目三:求出程序允许结果

#include<stdio.h>
#include<string.h>
int main()
{
	unsigned char puc[4];
	struct tagPIM
	{
		unsigned char ucPiml;
		unsigned char ucData0 : 1;
		unsigned char ucData1 : 2;
		unsigned char ucData2 : 3;
	}*pstPimData;

	pstPimData = (struct  tagPIM*)puc;
	memset(puc, 0, 4);

	pstPimData->ucPiml = 2;
	pstPimData->ucData0 = 3;
	pstPimData->ucData1 = 4;
	pstPimData->ucData2 = 5;

	printf("%02x %02x %02x %02x\n", puc[0], puc[1], puc[2], puc[3]);
	return 0;
}

分析:

一些注意事项

pstPimData = (struct  tagPIM*)puc;

(数组名表示首元素地址)此处通过强制类型转换把 unsigned char 类型的 数组puc 强制转换成了 tagPIM* 的类型。

---

pstPimData = (struct  tagPIM*)puc;
memset(puc, 0, 4);

此时的 pstPimData 这个指针指向 数组puc 的首元素地址。

memset函数 把 数组puc 内前 4个字节 的内容全部变为了 0,也就是说:数组puc 内部此时全部是 0。

此处需要注意的是:由于强制类型转换,指针pstPimData 认为它指向的是一个结构体,而 数组puc 则认为它指向的是一个数组。

-----

数据存储部分

pstPimData->ucPiml = 2;

 即要往 ucPiml 这个成员内部放入一个 2 ,也就是说,此时 数组puc 内部会变成这样:

但是接下来就会遇到问题了:

因为接下来的三个成员 ucData0、ucData1 和 ucData2 都是位段成员。而哪怕仅仅是 ucData0 这个成员通过[unsigned char]先向系统申请的 1个字节 的空间,这三个成员都用不完,因为 1bit + 2bit + 3bit = 6bit < 8bit

那么换言之,程序向 ucData0ucData2ucData3 写入的数据全部都会被存储在第二个字节内部,之后的两个字节完全不会被使用。即:

pstPimData->ucData0 = 3;
pstPimData->ucData1 = 4;
pstPimData->ucData2 = 5;

首先是 ucData0 ,现在我们知道:

  1. 3 的二进制序列是:00000011。
  2. 位段成员 ucData0 只能调用一个字节的空间。
  3. 一个字节的空间最多只能放入一个 1。

所以如果我们要存储 00000011 这个二进制序列,就会发生截断,截取这个二进制序列最尾部的 1,放入空间之中,成为:

以此类推,4 的二进制序列是 00000100 ,5 的二进制序列是 00000101 ,则有:

-----

最后的打印环节

printf("%02x %02x %02x %02x\n", puc[0], puc[1], puc[2], puc[3]);

ps:%02x 意为以十六进制的形式输出,且只输出两位。

接下来就是把二进制转换成十六进制了。值得注意的是,每四位二进制位正好对应一位十六进制位:

这就是最后的打印结果:

联合体的大小计算

题目四:计算一个联合体所占内存空间的大小。

#include<stdio.h>
union Un
{
	short s[7];
	int n;
};

int main()
{
	printf("%d\n", sizeof(union Un));
	return 0;
}

分析:

short s[7];        //可申请 14个字节 的空间。
int n;                //可申请 4个字节 的空间。

联合体的内存对齐,参考:笔记21-2

认为:

  • short类型的 数组s 虽然可以申请14个字节的空间,但依旧是 short类型 的,大小是 2个字节 ,所以默认对齐数仍然是 2
  • int类型的 n 大小是 4个字节 ,即使是在默认对齐数是 8 的情况下,默认对齐数也是 4

综上所述,默认对齐数中最大的对齐数就是 4

而联合体的特点就是多个成员共用同一块空间,所以联合体经常会出现一种情况:自身的大小是最大成员的大小,这里也出现了类似的情况。

但是,有一点需要注意:这里最大的成员 数组s 所占空间大小是 14个字节 ,14并不是4的整数倍,这不符合结构体内在的要求,所以需要扩充空间。

因此,最终这个联合体的大小就是 16个字节 。

程序执行的结果:

大小端的存储问题

题目五:求出程序的执行结果。

情景:本题要求小端字节序。

#include<stdio.h>

int main()
{
	union
	{
		short k;
		char i[2];
	}*s, a;

	s = &a;
	s->i[0] = 0x39;
	s->i[1] = 0x38;

	printf("%x\n", a.k);
	return 0;
}

分析:

联合体的存储方式参考:笔记21-2

大小端存储参考:笔记18

一些前提:

  • 联合体指针s 指向了 联合体a :

  • short类型的 k 和 数组i 共用一块空间,形如:

  • 按照十六进制的方式把38和39分别存储到 联合体a 中:

 现在就剩下打印部分:

printf("%x\n", a.k);

 注意:这里打印使用的是 a.k ,是把整个联合体的成员都当作一个 short类型 的变量来进行使用。这里就会涉及到一个大小端存储的问题

小端字节序:把低位数据放到低地址处。

大端字节序:把低位数据放到高地址处。

如果按照小端字节序还原存储在内存内的数据,就会变成:0x38 0x39。

这和打印结果(VS2022下是小端字节序)是一致的:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值