结构体、枚举以及联合类型在内存中的存储与大小计算

导读:

结构体,枚举,联合都是自定义类型,三种类型在内存中的存储大致类似但也有不同,下面我们来详细了解三种类型的定义、特点以及在内存中的存储方式和计算大小的方式,其中也含有位段的详解

一、结构体

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
数组是一组相同类型元素的集合
数据结构:描述的是数据在内存中存储和组织结构

1. 结构体的定义

struct tag
{
    
    
	member - list;
}variable - list;

也就是

struct 结构体名
{
成员列表
}变量名列表;

比如我们描述一个学生,定义一个学生结构体,里面包含学号、姓名、性别、年龄等

struct Stu
{
    
    
	char num[20];//学号
	char name[20];//名字
	char sex[5];//性别
	int age;//年龄
}; //分号不能丢

2. 结构体的调用

2.1 在结构体中引用另一个结构体

依旧是上面的结构体,想要在学生属性中多加一个出生日期

struct Date
{
    
    
	int month;
	int day;
	int year;
};
struct Stu
{
    
    
	char num[20];
	char name[20];
	char sex[5];
	int age;
	struct Date birthday;
}; 

深入思考是不是还可以自己引用自己

2.2 结构体的自引用

struct Node
{
    
    
	int data;//存放数据-数据域
	struct Node* n;//自引用
	//存放下一个节点的地址-指针域
};
int main()
{
    
    
	printf("%zd\n", sizeof(struct Node));
	return 0;
}

运行结果

3. 结构体变量的定义和初始化

我们会定义变量了,哪如何去用

struct Point
{
    
    
	int x;
	int y;
}p1 = {
    
     1, 2 };//声明类型的同时定义变量p1
struct Point p3 = {
    
     4,5 };//初始化:定义变量的同时赋初值。
struct Stu	//类型声明
{
    
    
	char name[15];
	int age;
};
struct Node
{
    
    
	int data;
	struct Point p;
	struct Node* next;//自引用
};
int main()
{
    
    
	int a = 10;
	int b = 3;
	struct Point p2 = {
    
     a,b };

	struct Stu s = {
    
     "zhangsan",20 };
	struct Stu s2 = {
    
     .age = 18,.name = "如花" };
	struct Node n = {
    
     100,{
    
    20,21},NULL };//结构体嵌套初始化
	printf("%s %d\n", s.name, s.age);
	printf("%s %d\n", s2.name, s.age);
	//printf("%d x = %d")
	return 0;
}

图片:运行结果

4. 结构体大小的计算

4.1 题目思考

想一下下面的代码运行结果是多少

struct S1
{
    
    
	char c1;
	int i;
	char c2;
};
struct S2
{
    
    
	char c1;
	char c2;
	int i;
};
struct S3
{
    
    
	double d;
	char c;
	int i;
};
int main()
{
    
    
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	printf("%d\n", sizeof(struct S3));
	return 0;
}

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

4.2 结构体在内存中的存储

结构体在内存中的存储遵循对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8

  1. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  2. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

4.3 画图详解

在这里插入图片描述而结构体总大小为最大对齐数的整数倍,这里是int类型最大对齐数4,所以取4的整数倍,从0 ~ 8共9个字节,所以取12
在这里插入图片描述
结构体总大小为最大对齐数的整数倍,这里是int类型最大对齐数4,所以取4的整数倍,从0 ~ 7共8个字节,所以取8
在设计结构体的时候,我们既要满足对齐,又要节省空间,就可以让占用空间小的成员尽量集中在一起。

5. 结构体传参

我们在结构体传参时,可以传值调用,也可以传址调用

struct S
{
    
    
	int data[1000];
	int num;
};
void print1(struct S t)
{
    
    
	printf("%d %d %d %d\n", t.data[0], t.data[1], t.data[2], t.num);
}
void print2(const struct S* ps)
{
    
    
	printf("%d %d %d %d\n", ps->data[0], ps->data[1], ps->data[2], ps->num);
}
int main()
{
    
    
	struct S s = {
    
     {
    
    1,2,3},100 };
	print1(s);//传值调用
	print2(&s);//传址调用
	return 0;
}

运行结果:
在这里插入图片描述
可以看到结果是相同的,但是我们平时传参时,首选print2函数形式传参。

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降

二、位段

位段又称为位域,其定义借助于结构体,即以二进制位为单位定义结构体成员所占存储空间。

1. 位段的定义

位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是
int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。

2. 位段的应用

struct A
{
    
    
	int _a : 2;//—— _a占2个bit位的空间
	int _b : 5;//—— _b占5个比特位
	int _c : 10;
	int _d : 30;
};
int main()
{
    
    
	printf("%d\n", sizeof(struct A));
	return 0;
}

在这里插入图片描述
下面我们来具体讲解位段的内存分配

3. 位段的内存分配

  1. 位段的成员可以是int 、unsigned int 、signed int 或者char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节(int )或者1个字节(char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

在这里插入图片描述

上诉代码中,定义的时int类型,所以是以四个字节的方式来开辟的,所以先开辟了32个bit位

a占用2个bit位,剩余30个bit位 b占用5个bit位,剩余25个bit位
c占用10个bit位,此时还剩15个bit位,并不足以给d用,所以在这里重新开辟四个字节——也就是32个bit位给d用
但是要注意的是,并没有紧接着c后面来存储d,而是直接在存储在新开辟的32个bit位的内存中

4. 位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。

三、枚举

枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举

1. 枚举类型的定义

enum Day//星期
{
    
    
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};
enum Sex//性别
{
    
    
	MALE,
	FEMALE,
	SECRET
};
enum Color//颜色
{
    
    
	RED,
	GREEN,
	BLUE
};

2. 枚举的取值

这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值

enum Sex
{
    
    
	//枚举的可能取值
	MALE,//枚举常量
	FEMALE,
	SECRET
};
enum Color
{
    
    
	RED,
	GREEN,
	BLUE
};
int main()
{
    
    
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	return 0;
}

在这里插入图片描述

2.3 练习

思考下面的代码结果

enum ENUM_A
{
    
    
	X1,
	Y1,
	Z1 = 255,
	A1,
	B1,
};
enum ENUM_A enumA = Y1;
enum ENUM_A enumB = B1;
int main()
{
    
    
	printf("%d %d\n", enumA, enumB);
	return 0;
}

联合体

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)

1. 联合体的定义

union Un
 {
    
    
 char c;
 int i;
 };

2. 联合体大小的计算

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

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

3. 联合体的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)
比如

union Un
{
    
    
	int i;
	char c;
};
union Un un;

int main()
{
    
    
	// 下面输出的结果是一样的吗?
	printf("%d\n", &(un.i));
	printf("%d\n", &(un.c));
	return 0;
}

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

猜你喜欢

转载自blog.csdn.net/qq_64818885/article/details/133361218