C语言——结构体

332 阅读5分钟

结构体使c语言有能力描述复杂对象。

例:描述一个人 —— 名字、性别、年龄 ……

要把多个类型整合在一起才能描述一个人,此时可以创建人的结构体类型

结构体类型声明

image.png

结构体成员类型可以是float、int、数组、指针、甚至其它结构体.

结构体变量声明

image.png

如果想省略struct,可以使用类型重命名typedef.

image.png

image.png

注意 结构体类型声明时并没有创建空间,创建结构体变量才会占用空间.

结构体变量初始化

image.png

结构体成员访问

1 结构体变量.结构体成员

2 结构体指针->结构体成员

typedef struct point
{
	int x;
	int y;
}point;//声明结构体类型坐标,并重命名

typedef struct power
{
	point way;
	int time;
	char arr[5];
}power;
int main()
{
	point a1;
	power a3;
	scanf("%d %d", &a1.x, &a1.y);
	scanf("%d %d %d", &(a3.way.x), &(a3.way.y), &a3.time);
	scanf("%s", a3.arr);//一般情况下,数组名本身是地址
	
	a1.x = 3;
	
	(a3.arr)[0] = 'c';     //a3.arr找到a3结构体变量的成员arr,
	                       //这是个数组名,可以用[ ]访问数组中的元素

	a3.way.x = 30;         //a3.way找到a3结构体变量的成员way
	return 0;              //因为way本身又是结构体变量,所以.x找到它的成员
}

结构体传参

如果函数传参传结构体变量:

形参是实参的临时拷贝,即会在栈区重新创建一个和该结构体变量一样大的空间,

把结构体变量的内容拷贝一份放进去。如果结构体过大,栈区的内存开销会比较大,性能会下降。

如果函数传参传结构体指针:

指针的大小是固定的,32位平台4个字节,64位平台8个字节,传指针依然能找到结构体变量的数据。

因此结构体传参时最好传结构体指针!!!

结构体内存对齐(计算结构体大小)

image.png

这说明:结构体内部的成员必然存在某些对齐现象,使得空间更大。

对齐规则

结构体变量开辟空间时,在内存中的起始位置也已经对齐了

如果起始位置没对齐,那下面的对齐就没有意义

(1) 每一个结构体成员都有一个对齐数.

对齐数 = 【编译器默认的对齐数】与【该成员大小】的 较小值.

(2) 偏移量:当前位置相对于结构体起始位置的距离.如:

image.png

(3)【第一个结构体成员的地址】与【结构体变量的地址】偏移量为0.

(4)【其它结构体成员的地址】与【结构体变量的地址】偏移量为 自身对齐数的整数倍.

(5)结构体总大小为最大对齐数(每个成员变量都有一个对齐数,不考虑默认对齐数)的整数倍.

image.png

image.png

(5)如果嵌套了其它结构体,嵌套的结构体要对齐到自己最大对齐数的整数倍处

结构体的整体大小就是最大对齐数(要和嵌套结构体的最大对齐数做比较)的整数倍.

简单理解:【嵌套结构体】的对齐数就是【嵌套结构体】内部的最大对齐数.

#include <stdio.h>
struct A
{
	char aChar; //1 < 8 ? 1 : 8 对齐数为1
	int aInt;      //4 < 8 ? 4 : 8 对齐数为4
};
struct B
{
	char bChar;       //对齐数为1
	struct A bStructA;//内部最大对齐数为4
	int bInt;         //对齐数为4
};
int main()
{
	printf("%d\n", sizeof(struct A));
	printf("%d\n", sizeof(struct B));
	return 0;              
}

struct A结构体类型变量的内存存储:

image.png

struct B结构体类型变量的内存存储:

image.png

为什么存在内存对齐?

平台原因

某些硬件平台只能在某些地址处取某些特定类型,否则硬件异常。

比如某些硬件只能在4的倍数地址处进行数据读取或存储.

所以在存储数据时最好存放在对齐的位置上,使读取数据方便且不出错.

性能原因

内存对齐是一种以空间换时间的做法.

为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问只需一次.

例:在32位机器中,一次可以操作32bit/4byte的数据,想拿出结构体成员b的数据,需要2次操作.

image.png

image.png

对于b的读取,不考虑内存对齐要两次读取才能完整读出来,

而考虑内存对齐只需要一次就全部读取出来,第一次的读取没有意义.

这样对应b的读取,虽然浪费了一点空间,但效率会有提高.

合理设计结构体

在设计结构体时,我们既想满足内存对齐,又想节省空间:

方案1:让占用空间小的成员尽量集中在一起

由于占用空间较小,对齐数也通常较小,那么那些可能浪费的空间就放得下这些小成员,减少浪费.

方案2:修改默认对齐数

#pragma pack(数字)

#pragma pack(4)//设置默认对齐数为4
struct A
{
	char aChar;          //1 < 4 ? 1 : 4 对齐数为1
	double aDouble;      //8< 4 ? 8 : 4 对齐数为4
};

#pragma pack()//取消设置的默认对齐数,还原为编译器默认
struct B
{
	char bChar;          //1 < 8 ? 1 : 8 对齐数为1
	double bDouble;      //8< 8 ? 8 : 8 对齐数为8
};
int main()
{
	printf("%d\n", sizeof(struct A));
	printf("%d", sizeof(struct B));
	return 0;
}

image.png