自定义类型(结构体、联合体、枚举)
- 一、结构体
- (一)结构体的内存对齐
- 1、结构体内存对齐规则
- (1)引子
- (2)offsetof 宏函数
- (3)内存对齐原理
- (4)自定义默认对齐数
- 2、为什么要内存对齐
- (1)性能原因
- (2) 平台原因
- (二)结构体实现位段
- 1、位段的定义
- 2、位段的储存
- 3、位段的不可跨平台性
- 4、位段的应用
- 二、枚举
- (一)枚举的定义
- (二)枚举的优点
- 三、联合体
- (一)联合体的储存
- (二)联合体的应用
- 四、结束语
一、结构体
(一)结构体的内存对齐
1、结构体内存对齐规则
(1)引子
结构体占多少内存呢,是不是各个变量的内存之和呢,先用一段代码来验证我们的猜想吧。下面代码中,如果我们猜想正确,那么打印结果就应该是三个变量的字节数之和,答案为6.
(2)offsetof 宏函数
offsetof (type,member) 返回成员变量number距离结构体首地址的偏移量(单位是字节)
我们对结构体S的a, b, c,分别查看他们的偏移量。
根据这个结果,我们可以画出下面的内存图解。
(3)内存对齐原理
1、结构体的第一个成员变量对齐到和结构体首地址偏移量为0的地址处
2、其它成员变量要对齐到对齐数的整数地址处。
对齐数 = 编译器的默认对齐数 和 该结构体成员变量的大小 中的较小值
3、结构体大小为最大对齐数的整数倍。
4、如果结构体嵌套结构体,嵌套的结构体变量要对齐到自己成员变量中最大对齐数整数倍的地址处。结构体大小就是所有成员(包含结构体中的成员)中最大对齐数的整数倍。
求下面代码的输出结果。
struct S1 {
double a;
char b;
int c;
};
struct S2 {
char a;
struct S1 b;
double c;
};
int main() {
printf("%zd", sizeof(S2));
return 0;
}
要求结构体S2的大小,我们首先要求结构体S1的大小,分析如下。
接着再求结构体S2的大小
分析结果为32和我们预料的一致。
(4)自定义默认对齐数
使用 #pragma pack(int val)可以设置默认对齐数。
例如:
#pragma pack(1)//将默认对齐数设置为1
struct S {
char c1;
int n;
char c2;
}
#pragma pack() //重置默认对齐数
2、为什么要内存对齐
所以说不仅步骤麻烦,还消耗了内存,为什么还要内存对齐呢。用空间换时间!
(1)性能原因
为了访问未对齐的数据,处理器需要访问多次,而处理对齐的数据,处理器访问次数减少。
struct S {
char a;
int b;
};
在32位平台中,我们一次可以访问四个字节。下面我们访问变量 b 并用内存对齐和非内存对齐来对比
内存对齐
非内存对齐
通过非内存对齐,发现我们起码要通过两次才能拿到 int 成员变量。
(2) 平台原因
不是所有平台都能访问任意地址上的任一数据。
(二)结构体实现位段
1、位段的定义
位段中的位指的是二进制位,和结构体类似,有两点不同
1、位段的类型只能是 int 、signed int、unsigned int 或者是char(整形家族),C99中引入了其它类型。
2、位段后面比较加冒号和数字,表示为该变量分配多少个比特位的空间。
如下:
struct S {
int a : 2;
int b : 6;
int c : 4;
int c : 4;
};
2、位段的储存
位段也有自己的储存方式,我们分析下面一段代码进行体会
struct S {
int a : 3;
int b : 4;
int c : 5;
int d : 4;
};
int main() {
printf("%d", sizeof(struct S));
return 0;
}
注意:
位段在初始化变量时候不可以用scanf,因为&符号是以一个字节为单位,显然如果一个字节里面有多个变量的话,会引发错误。
3、位段的不可跨平台性
1、数据类型解释的不确定性:
有符号与无符号:位段中的int类型成员是否被当作有符号数还是无符号数,在不同的编译器和平台上可能存在差异。
位数限制:位段中成员的最大位数在不同的编译器和平台上可能有所不同。例如,在16位机器上,位段的最大位数可能为16,而在32位机器上可能为32。
2、内存分配方式的不确定性:
分配方向:位段成员在内存中的分配方向(从左到右或从右到左)在C语言标准中并未明确定义,这可能导致在不同的编译器和平台上出现不同的内存布局。
空间利用:当一个结构体包含多个位段成员,且后一个成员无法完全容纳在前一个成员剩余的位中时,是舍弃剩余位还是尝试利用这些位,也是不确定的。
4、位段的应用
网络协议中用来包装IP数据报(就好像外卖一样,需要填写目的地的信息),而位段进行封装就会使得包裹更加便捷。
二、枚举
(一)枚举的定义
我们可以把生活中一些事物所对应的情况一一列举处来,就是枚举。
如下,其中enum就是枚举类型,{}里的内容就是枚举常量。
变量中的枚举常量默认赋初始值为0 1 2 3 … 也可以自定义赋值。自定义赋值时,可以选择性赋值,没有赋值的会序列化自动赋值。
(二)枚举的优点
枚举的优点用以下代码可以充分体现。
enum Option
{
EXIT,//0
ADD,//1
SUB,
MUL,
DIV
};
void menu()
{
printf("**********************************\n");
printf("****** 1. add 2. sub ******\n");
printf("****** 3. mul 4. div ******\n");
printf("****** 0. exit ******\n");
printf("**********************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case SUB://
break;
case ADD:
break;
case MUL:
break;
case DIV:
break;
case EXIT:
printf("退出\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
三、联合体
(一)联合体的储存
我们直接将联合体的储存是因为他和结构体上面就一个本质差别:联合体成员公用同一块空间(所以联合也叫共用体)。
规则:
1、联合的大小至少是最大成员的大小。
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
(二)联合体的应用
联合体和结构体嵌套使用,可以节省很多内存:
例如我们需要使用该结构体,如果前面三个公共属性必选,而特征属性非必选,我们可以设置下面的代码来减少结构体的内存:
四、结束语
到此这篇文章就告一段落了,小编还会继续用心对待每一篇文章,与大家一起进步,非常有幸能得到大家的支持!