c语言结构体详解

结构体详解

重点讲解结构体的内存对齐

结构体是什么

结构体(struct)是C语言中一种复合数据类型,它允许将多个不同类型的数据项组合成一个单一的数据类型。结构体非常适合用来存储和表示具有多个属性的实体,如学生的姓名、学号、年龄等。

结构体的基本结构
在这里插入图片描述
**注释
struct 是结构体的关键词
str是结构体的名字,是自定义的
char int float 是成员的数据类型
num name这些则是成员名
str1 str2是结构体变量 且是全局变量
上面的结构体则是定义了两个具有4个成员的结构体变量str1,str2
定义结构体时,结构体名字–str和变量str1,str2可有可无
但结构体结尾必须需加上英文分号
结构体定义本身不会分配内存,它只是一个模板。当创建结构体变量时,才会为其分配内存空间
在主函数使用结构体时,结构体的声明必须在调用之上,否则会报错
**

结构体的使用

结构体的初始化

在这里插入图片描述

打印结果
在这里插入图片描述
只要printf打印的内容 如名字是:%s 与str1.name一一对应,其他也如此,即使顺序不同,也可以得到正确打印

结构体的直接访问

在上面的中,我们看到了结构体的访问形式如下
<结构体类型的变量名>.<成员名> (使用中<>去掉)
其中这里面的.是结构体直接访问的操作符
在这里插入图片描述

结构体的间接访问

是->操作符 用于间接访问结构体指针指向的对象的成员
如果我们有一个指向结构体的指针,而不是结构体变量本身,此时就需要使用间接访问操作符

struct S
{
	int arr[5];
	int a;
	float b;
};
void print(struct S* ps)
{
	for (int i = 0; i < 5; i++)
		printf("%d ", ps->arr[i]);
	printf("\n");
	printf("%d\n", ps->a);
	printf("%.2f", ps->b);
}
int main()
{
	struct S s = { {1,2,3,4,5},10,3.14f };
	print(&s);//此处对s进行取地址,是为了得到结构体的地址.从而使指针指向结构体
	return 0;
}

结果
在这里插入图片描述

总结,具体使用哪种访问,要看具体情况,这才能达到最佳效果

typedef重命名

type是类型 def是定义
:对于复杂的变量声明,typedef 可以显著提高代码的可读性。

typedef struct S
{
	int num;
	char name[20];
	int age;
	float high;
}s1,s2,s3;  //此处即命名了三个结构体  typedef struct S s1   typedef struct S s2   typedef strucr  S s3

在其他地方的应用

int main()
{
	typedef int INT;
	INT num = 0;//等价于int num 此处将int 命名为了 INT
	typedef char ch;
	ch s = 'a';
	typedef int A[3];
	A arr[3];
	return 0;
}

结构体的内存对齐

引入

#include <stdio.h>
struct S
{
	char a;
	char b;
	int c;
	char d;
};
int main()
{
	struct S s = { 0 };
	printf("%zd", sizeof(s));
	return 0;
}

大家觉得这打印结果应为多少

相信不少人认为a , b, d,e各1个字节 d4各字节, d所以总和应为7个字节
但结果却并非如此
打印结果
在这里插入图片描述

出现这种情况的原因是结构体的内存对齐,接下来让我们一起了解内存对齐

正言

结构体的对齐规则:

在这里插入图片描述
对于该规则的讲述,拿上面引言的讲解为例,
讲vs中内存抽象为图像
在这里插入图片描述

由第一个规则知起始地点为0,即起始位置(此时偏移量为0)

又因为第一个元素为char类型 大小为1个字节,所以占据一个格子

在这里插入图片描述

第二个数据类型同样为char 大小一个字节,且vs的默认值为8
所以最小对齐数为char的大小—1
同时第二个数据存入内存的起始地址为1,是对齐数的整数倍,无需改变起始位置

占用完内存后变为
在这里插入图片描述

第三个数据类型为int 大小为4,而vs默认值为8,所以对齐数为4
由规则1知,起始位置要是对齐数的整数倍,
大于2且是4的最小整数倍为4
所以需浪费两个格子的内存
此时变为
在这里插入图片描述
int 为4个字节大小,所以再次变化
在这里插入图片描述

第四个数据类型为char 大小为1
而起始位置8是1的整数倍,所以无需浪费内存
变化后为
在这里插入图片描述

由第3条规则
上面数据的最大对齐数为4,而终止位置9并非是4的整数倍,所以要继续浪费内存来终止在这里插入图片描述
而大于9且为4的最小整数倍为12
所以再浪费3个格子的内存,从而终止
在这里插入图片描述

相信有上面的讲解,你对此有一定了解
结下来试试这个
在这里插入图片描述

答案 此处交给你们自行解答,后面还有练习进行补充
在这里插入图片描述

结构体的内存对齐的最后一条规则

在这里插入图片描述

#include <stdio.h>
struct S3
{
	double d;
	char c;
	int i;
};
struct S2 
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{
	struct S2 s2 = { 0 };
	printf("%d", sizeof(s2));
	return 0;
}

讲解

按顺序,先是s2.c1 ,该为字符,大小为1个字节,<8,
所以对齐数为1
又因为起始位置偏移量为0
所以
在这里插入图片描述

接着就是嵌套结构体了,开始进入结构体s3
s3.d为double类型,大小为8个字节 == 8,所以对齐数为8
又因为起始位置要为对齐数的整数倍
所以最终为
在这里插入图片描述

接着s3.c 数据类型为char与上面类似,不再细说,看图
在这里插入图片描述

接着是s3.i 数据类型为int <8 ,所以对齐数为4
又因为起始位置要是对齐数的整数倍
所以最终为
在这里插入图片描述

最后再返回s2.d 为double类型,大小为8个字节 == 8 所以最小对齐数为8
由对齐规则的第一条规则知,起始位置刚好为对齐数的整数倍,所以不需舍去内存
在这里插入图片描述
此处32是最大对齐数8(包含嵌套结构体的最大对齐数)的整数倍,所以停止

打印结果
在这里插入图片描述

同理,对于大于等于2的结构体嵌套数,一样,都是一个一个元素排

为什么存在结构对齐及优解

在这里插入图片描述
重点:是拿空间来换取时间的做法

取优
此时聪明的你们,应该发现了,即使一个结构体的成员名及数据类型相同,但在不同顺序下排列,最后所占的内存大小不一样

此处建议:在设计结构体为了即满足对齐,又节省空间:
可让占用空间小的成员尽量集中在一起

修改默认对齐数

#pragna这个预处理指令,可以改变编译器的默认对齐数

#include <stdio.h>
#pragma pack(1)//设置默认对齐数为1
struct s
{
	char a;
	char b;
	int c;
	char d;
};
#pragma pack()//取消设置的对齐数,还原为默认
int main()
{
	printf("%d", sizeof(struct s));
	return 0;
}

结果,可以看到此处的结构体完全与我们的第一个例子一样,但因为默认对齐数的不同,最后结果不同在这里插入图片描述

练习

在这里插入图片描述
答案为:12

在这里插入图片描述

答案 16 12

结构体实现位段

什么是位段

段的定义形式类似于结构体,但其成员后需跟一个冒号和数字,表示该成员占用的位数

struct bs {
	unsigned int a : 3; // 占用3位bit位
	unsigned int b : 5; // 占用5位bit位
	unsigned int c : 8; // 占用8位bit位
};

**注意事项:

1.其中 bs就是一个位段类型
2. 有些程序员的风格喜欢在数据类型跟命名之间加上下划线
例如:unsigned int _a : 3;(这仅仅是个人习惯,不会影响最终结果)

3.跟结构体相比,位段成员必须 int、unsigned int、signed int 或 char 类型

位段的内存分配

段的内存分配涉及以下几个原则:
位段的成员可以是 int、unsigned int、signed int 或 char 类型。
位段的空间按照需要以4个字节(int)或1个字节(char)的方式来开辟。
位段的成员在内存中从右向左分配空间,如果遇到不足位段成员大小的空间,则直接丢弃,重新开辟一个字节的空间分配。

直接举例说明:

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 6;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

接下来画图说明
10 转为二进制为00000000000000000000000000001010
12 转为二进制为00000000000000000000000000001100
3转为二进制为0000000000000000000000000000000011
4转为二进制为0000000000000000000000000000001100
读数都是从低位向高位读

而char a : 3;则只占3个bit位即
在这里插入图片描述
char b : 4;占4个bit位
在这里插入图片描述

char c : 5;占5个bit位 而上面这一个字节只剩1个bit位,不足则舍去,再开一个新的字节
在这里插入图片描述
char d : 6;占6个bit位而上面这个字节只剩3个bit位,所以再开一个新的字节
在这里插入图片描述

位段跨平台问题

在这里插入图片描述
解决方法:根据不同的平台写不同的代码

位段使用的注意事项

在这里插入图片描述
错误做法
在这里插入图片描述

正确做法 先对一个变量进行赋值,再令位段的成员等于该变量
在这里插入图片描述

在使用Keil集成开发环境进行ARM微控制器编程时,可能会遇到“找不到编译器 Missing: Compiler Version 5”的错误提示,这通常是由于Keil没有正确配置或安装了不完整的ARM Compiler v5.06导致的。本文将详细介绍如何解决这个问题。 我们需要了解ARM Compiler是什么。ARM Compiler是ARM公司开发的一套编译工具链,它包括了编译器、链接器、汇编器等组件,用于将C/C++源代码转换为适用于ARM架构处理器的目标代码。在Keil中,它用于构建和优化针对ARM芯片的应用程序。 在错误信息中提到的"arm complier v5.06",指的是ARM Compiler的版本号5.06。这个版本可能与你的Keil安装不兼容或者未被正确识别。解决这个问题的步骤如下: 1. **检查安装**:确保你已经安装了ARM Compiler v5.06。通常,这个工具会在安装Keil μVision时一起安装,但如果没有,你需要单独下载并安装。可以从ARM官网或者Keil的官方网站获取相应版本的编译器。 2. **配置Keil路径**:在Keil的安装目录下找到`TOOLS.INI`文件,这是一个配置文件,用于指定编译器的位置。确保其中的路径指向了你安装的ARM Compiler v5.06的目录。 3. **更新项目设置**:在Keil μVision中,打开你的项目,然后选择“Project” > “Options for Target” > “Toolchain”。在“Compiler”选项卡中,确认“Compiler version”已经设置为“v5.06”。如果未自动识别,可以手动输入正确的路径。 4. **环境变量**:有时,即使设置了正确的路径,Keil仍然无法找到编译器,可能是因为系统环境变量未设置好。确保`PATH`环境变量包含了ARM Compiler的bin目录,这样系统在启动Keil时才能找到编译器。 5. **重启Keil**:完成上述设置后,关闭并重新启动Keil μVision,让更改生效。如果问题仍未解决,尝试卸载并重新安装Keil和ARM Compiler。 在提供的文件列表"arm506"中,可能包含了解决这个问题所需的一些资源,如安装程序、补丁或配置文件。如果你已下载这个文件,可以按照以下步骤操作: - 解压缩文件,通常会得到一个包含编译器可执行文件的目录。 - 将这个目录路径添加到Keil的`TOOLS.INI`文件或系统环境变量`PATH`中。 - 如果是补丁文件,按照说明应用到Keil或ARM Compiler的安装目录。 通过以上步骤,大部分情况下都能解决“找不到编译器 Missing: Compiler Version 5”的问题。如果问题依然存在,可能需要检查网络连接,因为某些情况下,Keil需要访问在线许可证服务器。此外,确保你的Keil版本与ARM Compiler版本兼容,不同版本的Keil可能支持不同的ARM Compiler版本。在升级或更新任何组件时,务必查阅官方文档以获取详细信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值