【C语言】自定义类型:结构体类型

一,结构体类型简介

在设计程序时,最重要的步骤之一就是选择表示数据的方法,下面给出七种C语言常见的的内置数据类型:

数据类型类型标识符
字符型char
短整型short
整型int
长整型long
长长整型long long
单精度浮点型float
双精度浮点型double

而在许多情况下,内置的数据类型并不能满足我们的需求。在实际应用中,我们经常需要由不同类型的数据来构成一个整体:比如描述一个人,把他看成一个整体他可以由 年龄身高名字 , 等数据组成而想要表示这些数据,你可能会想到使用数组,但这是通常是行不通的,因为数组里面存放的都是相同类型的数据,而这些数据都拥有不同的类型,年龄可以是整形身高应该是浮点型,而名字应该是字符型。

为此,C语言提供了一个自定义类型:结构体类型,它可以包含各种类型的数据,用来描述这类较复杂的问题。

二,结构体的创建和初始化

1,结构体的创建

声明方式如下:

struct tag{     //用花括号括起来的是成员列表;
	member1;
	member2;
		...
}variable-list;

其中:

  • struct:C语言中的关键字,表示结构体的声明。
  • tag :结构体的名字,也就是 结构体标识符
  • member1: 和member2一样,他们都是这个结构体里面的成员,可以是C语言里边的任意一种内置数据类型,当然,也可以是其他的结构体变量
  • variable-list:结构体变量名。

在声明以上结构体时,也可以不写 variable-list 而在后面的程序中写上:

struct tag variable-list;

显然,这和我们用的内置类型变量命名方式一样。

2,初始化

我们可以试着用结构体描述一个学生,这里还是给出一个例子:

struct Stu{
	char name[20];  //名字
	int age;        //年龄
	float weight;   //体重
};
int main() {
    //为其命名和赋值
    struct Stu Student={"zhangsan",19,120};

打印方法如下(用 变量名 . 结构体成员名 来访问):

printf("名字:%s\n", Student.name);
printf("年龄:%d\n", Student.age);
printf("体重:%f", Student.weight);

输出结果:在这里插入图片描述

三,结构中存在的内存对齐问题

既然结构体变量也是一个变量,那么它也就可以用操作符 sizeof 来计算所占的字节长度。
如以下代码:

struct S1 {
	char c1;
	char c2;
	int a;
}s1;
int main() {
	printf("%d\n", sizeof(s1));
	return 0;
}

那么,这个输出结果是怎么的呢?
因为结构体的大小就是结构体中各个成员大小的总和,计算结构体的大小时,要将每个成员的大小相加,所以可能会想到输出的结果:6
然而,事实并非如此!!!
这就是它的输出结果:在这里插入图片描述
可以看到,在VS2022,X86环境下,它输出的结果为 8 ,而不是我们所期望的 6

这是因为结构体的大小计算通常遵循一些规则,这取决于编译器和系统的规范,以确保内存布局是有效和紧凑的。

结构体的大小通常按照以下规则计算:

成员对齐(Member Alignment): 结构体中的每个成员都有一个对齐要求,这个要求通常是基于成员的数据类型。例如,整数通常要求4字节对齐,双精度浮点数通常要求8字节对齐。编译器会确保结构体的每个成员按照其对齐要求排列,以保证访问成员时的效率和可靠性。编译器也存在默认对齐数,VS2022中,这个对齐数的大小是 8 ,而某个成员的对齐数=编译器默认的一个对齐数与该成员大小的较小值。

填充字节(Padding Bytes): 由于对齐要求,结构体中的成员可能会导致一些填充字节的添加,以确保成员按照正确的边界对齐。这些填充字节通常不存储任何数据,只是用于对齐。填充字节的大小取决于编译器和体系结构。

计算机由低地址向高地址存放数据,于是根据上述代码,可以画出以下图:

在这里插入图片描述
可以看到,这个结构体所占的字节是 8 。计算机在存放整形 a 时,跳过了两个字节,放在了后面,这就是因为 成员对齐 而跳过的那两个字节,通常不存储任何数据,也就是造成了内存浪费,这就是因为 填充字节
而还有很重要的一点是,结构体中创建成员的先后顺序

比如我把上面的代码改一下,再加一个结构体 S2

struct S1 {
	char c1;
	char c2;
	int a;
}s1;
struct S2 {
	char c1;
	int a;
	char c2;
}s2;
int main() {
	printf("%d\n", sizeof(s1));
	printf("%d\n", sizeof(s2));
	return 0;
}

其运行结果:在这里插入图片描述
画出 S2 在内存存放的方式:
在这里插入图片描述

这里需要提到的是,除了 1,2,3块空间被浪费了,9,10,11空间也同样被浪费了!!!这是因为结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。当c2存放过后,已经有了9个字节,而还要凑够 int 大小的整数倍 12 ,于是又填充了三个空字节。

这里提一句 #pragma pack 指令: 一些编译器允许使用 #pragma pack 指令来控制结构体的对齐方式。这可以用来改变默认的对齐规则,以便更好地满足特定需求。但也需要谨慎使用,因为它可能会导致不同平台上的不一致性。

可见在创建结构体时, 不正确的对齐或没有考虑对齐规则可能导致内存浪费,因为填充字节会占用额外的内存空间。这可能会影响数据结构的大小和内存使用效率。

既然内存对齐会导致空间浪费,那为什么要内存对齐?

因为它能够提高系统的整体性能。主要原因如下:
1. 提高数据访问效率
大多数现代CPU在访问内存时,都是以字(Word)为单位进行的。如果数据没有对齐,CPU就需要做两次内存访问操作,效率会降低。通过对齐,CPU可以一次完成整个数据的读写,提高了访问效率。
2. 避免CPU错误 有些CPU对未对齐的内存访问是不被允许的,会引发硬件异常或系统崩溃。进行对齐可以避免这种情况发生。
3. 提高并行计算能力
当多个线程同时读写相邻的内存地址时,如果没有对齐,就可能产生冲突,需要加锁等机制进行同步。对齐后,不同数据占用不同的缓存行,可以减少同步开销,提高并行计算能力。
4. 优化缓存利用率
由于CPU读取数据时是以缓存行为单位,如果多个变量在同一缓存行中,则只需要加载一次,可以提高缓存命中率。对齐后,不同变量更容易落入不同的缓存行。

这就是结构中存在的内存对齐问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值