目录
注:
本笔记参考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 。
那么换言之,程序向 ucData0、ucData2 和 ucData3 写入的数据全部都会被存储在第二个字节内部,之后的两个字节完全不会被使用。即:
pstPimData->ucData0 = 3;
pstPimData->ucData1 = 4;
pstPimData->ucData2 = 5;
首先是 ucData0 ,现在我们知道:
- 3 的二进制序列是:00000011。
- 位段成员 ucData0 只能调用一个字节的空间。
- 一个字节的空间最多只能放入一个 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下是小端字节序)是一致的: