嵌入式C语言:什么是共用体?
在嵌入式C语言编程中,共用体(Union)是一种特殊的数据结构,它允许在相同的内存位置存储不同类型的数据。意味着共用体中的所有成员共享同一块内存区域,因此,在任何给定时间,共用体只能有效地存储其成员之一的值。当存储一个新值时,它会覆盖共用体中之前存储的任何数据。
一、共用体的定义
共用体通过关键字 union 来定义,后面跟着共用体的名称和一对花括号,花括号内是共用体的成员列表。每个成员的定义与结构体中的成员定义类似。
union MyUnion {int i;float f;char str[10];
};
union MyUnion 可以存储一个 int、一个 float 或一个9字符的字符串(因为字符串的最后一个位置需要存储空字符 \0 以表示字符串的结束)。但是,由于这些成员共享内存,所以不能同时存储所有这些值并期望它们都有意义。
二、共用体的内存布局
2.1. 成员存储位置
共用体的所有成员都存储在同一块连续的内存空间中,它们的起始地址相同。例如:
union Example {int num;char ch;float f;
};
在这个共用体中,num、ch和f都从同一块内存空间的起始位置开始存储。
2.2. 内存大小确定
共用体的内存大小等于其最大成员的大小。这是为了确保能够容纳所有成员中占用空间最大的数据类型。比如在上述例子中,如果int类型占 4 个字节,char类型占 1 个字节,float类型占 4 个字节,那么union Example的大小就是 4 个字节。因为要保证这块内存空间既能存储int类型的数据,也能存储float类型的数据。
2.3. 内存覆盖情况
由于所有成员共享同一块内存,当给其中一个成员赋值时,会覆盖其他成员在内存中的值。例如:
union Example example;
example.num = 10;
// 此时example.ch和example.f的值是不确定的,因为它们在内存中的值被example.num的赋值操作覆盖了example.f = 3.14;
// 此时example.num和example.ch的值又被example.f的赋值操作覆盖了
2.4. 特殊情况
如果共用体中有数组成员,那么数组的大小将作为确定共用体内存大小的关键因素。比如:
union AnotherExample {int num;char ch;float f;char str[10];
};
在这种情况下,如果char str[10]的大小(10 个字节)大于int、char和float类型的大小,那么union AnotherExample的大小就是 10 个字节。并且在使用共用体时,对str数组的操作会影响到其他成员在内存中的值,反之亦然。
三、共用体成员访问
可以通过共用体变量来访问其成员,例如:
#include <stdio.h>// 定义共用体
union Data {int i;float f;char c;
};int main() {union Data data;// 给整型成员赋值data.i = 10;printf("data.i = %d\n", data.i);// 给浮点型成员赋值,会覆盖之前整型成员的值data.f = 3.14f;printf("data.f = %f\n", data.f);// 给字符型成员赋值,会覆盖之前浮点型成员的值data.c = 'A';printf("data.c = %c\n", data.c);// 再次访问整型成员,由于其值已被覆盖,结果是未定义的printf("data.i (after modification) = %d\n", data.i);return 0;
}
定义了一个共用体Data,它包含int、float和char三种不同类型的成员。通过共用体变量data,可以分别给不同的成员赋值并访问。需要注意的是,每次给一个成员赋值时,都会覆盖其他成员在内存中的值。所以在访问成员时,要确保访问的是最近一次赋值的成员,否则可能得到未定义的结果。例如,在给data.c赋值后再访问data.i,此时data.i的值是未定义的。
四、共用体特点与用途
4.1. 特点
内存共享:共用体所有成员共享同一块内存区域,其内存大小等于最大成员的大小。这使得在同一时刻,只有一个成员的值是有效的,对一个成员赋值会覆盖其他成员在内存中的值 。
4.2. 用途
- 节省内存:共用体(Union)在嵌入式系统中非常有用,特别是在需要节省内存的场景中。当程序需要在不同时刻存储不同类型的数据,但不需要同时存储这些数据时,共用体可以共享同一块内存区域,从而节省空间。
- 数据类型转换:共用体还可以用于不同类型数据之间的转换。例如,在处理网络通信或文件格式时,可能需要将整数、浮点数或字符串等数据类型相互转换。共用体提供了一种方便的方式来实现这些转换。
- 访问硬件寄存器:在嵌入式系统中,硬件寄存器的访问是常见的操作。共用体可以用于映射这些寄存器,并允许以不同的方式(如字节、半字、字等)访问它们。这对于读取和设置寄存器的特定位或位域非常有用。
五、使用场景
5.1.通信协议数据解析
在通信协议中,根据不同的命令类型会接收不同格式的数据,但同一时刻只会接收一种类型的数据。例如,在一个简单的传感器网络通信协议中,可能有温度传感器数据、湿度传感器数据和光照传感器数据等不同类型的数据包。可以定义一个共用体来存储接收到的数据:
union SensorData {int temperature;float humidity;unsigned int light;
};
这样,在接收到不同类型的数据时,可以通过共用体来灵活存储和解析,节省内存空间。
5.2. 硬件寄存器访问
在嵌入式系统中,经常需要访问硬件寄存器,这些寄存器可能包含不同类型的数据。例如,一个控制外设的寄存器,既可以以字节的形式访问其某个位域,也可以以整数的形式访问整个寄存器的值。可以使用共用体来方便地进行访问:
union RegisterData {unsigned char byte_data;unsigned int int_data;
};
通过这个共用体,可以根据需要以不同的方式操作同一个寄存器。
5.3. 数据类型转换
可以利用共用体来实现不同数据类型之间的转换。例如,将一个整数以字节的形式存储在共用体中,然后以字符数组的形式访问这些字节,从而实现整数到字节数组的转换。这在数据的传输和存储格式转换场景中很实用。以下是一个示例:
union DataConversion {int int_value;char byte_array[4];
};
通过这个共用体,可以方便地将整数转换为字节数组,或者将字节数组转换为整数。
5.4. 节省内存空间
当程序需要在不同时刻使用不同类型的数据,但不需要同时存储这些数据时,共用体可以节省内存空间。例如,在一个资源受限的嵌入式设备中,可能需要记录不同类型的传感器数据,但在某一时刻只需要存储一种传感器的数据。可以使用共用体来存储这些数据:
union SensorValue {int temperature;float pressure;long humidity;
};
这样,无论存储哪种传感器的数据,都只需要占用一份内存空间。
六、注意事项
6.1. 内存布局与对齐
- 内存共享:共用体的所有成员共享同一块内存空间。因此,修改一个成员的值会影响其他成员的值,因为它们实际上占用的是同一段内存。
- 内存对齐:编译器可能会为了内存对齐在共用体的成员之间插入填充字节。可能导致共用体的大小大于其最大成员的大小。内存对齐是为了提高处理器访问内存的效率,但可能会增加内存使用。
- 大小端问题:不同硬件平台可能采用不同的大小端模式(大端或小端)。在编写涉及共用体的跨平台代码时,需要注意大小端问题,确保数据在不同平台上的正确性。
6.2. 访问与修改成员
- 避免同时访问多个成员:由于共用体的成员共享内存,因此同时访问多个成员是没有意义的,因为它们的值会相互覆盖。在编程时应避免这种情况。
- 使用正确的运算符:访问共用体成员时,应使用
.运算符(对于共用体变量)或->运算符(对于指向共用体的指针)。
6.3. 函数参数与返回值
- 不能作为函数参数或返回值:在标准C语言中,共用体变量不能直接作为函数参数传递,也不能作为函数的返回值。但可以使用指向共用体变量的指针作为函数参数或返回值。
- 传递指针而非值:如果需要在函数内部修改共用体的成员,应传递指向共用体的指针而非其值。这样可以避免在函数调用过程中进行不必要的内存复制,并允许函数直接修改原始数据。
6.4. 类型安全性
- 类型转换:虽然共用体可以实现不同类型数据之间的转换,但这种转换是隐式的,不进行类型检查。因此,在使用共用体进行类型转换时需要谨慎,确保转换是安全的。
- 避免类型混淆:在编写涉及共用体的代码时,应清晰地区分不同成员的类型和用途,避免类型混淆导致的错误。
6.5. 初始化方式
共用体的初始化方式与结构体有所不同。只能对共用体的第一个成员进行初始化,例如:
union Data {int i;float f;
};
union Data data = {10}; // 初始化共用体的第一个成员i为10
如果要初始化其他成员,需要在定义共用体变量后单独进行赋值操作。
6.6. 访问时机与数据一致性
由于共用体成员共享内存,访问成员的时机非常重要。在对一个成员赋值后,如果没有及时访问该成员,而是先访问了其他成员,可能会得到未定义的结果。例如:
union Data {int i;char c;
};
union Data data;
data.i = 0x12345678;
// 先访问data.c,此时data.c的值可能是不确定的
printf("%x\n", data.c);
// 再访问data.i,其值可能已经因为之前对data.c的访问而改变
printf("%x\n", data.i);
6.7. 编程实践
- 明确用途:在定义共用体之前,应明确其用途和成员类型。确保共用体的设计符合实际需求,避免不必要的复杂性。
- 注释与文档:由于共用体的使用可能增加代码的复杂性,因此应在代码中添加足够的注释和文档,以便其他开发人员理解其用途和工作方式。
- 测试与验证:在编写涉及共用体的代码后,应进行充分的测试和验证,确保其在不同平台和条件下的正确性。
七、总结
共用体(Union)在嵌入式C语言中是一种特殊的数据结构,允许在相同的内存位置存储不同类型的数据。主要用于节省内存空间、数据类型转换以及访问硬件寄存器等场景。使用共用体时需注意内存对齐、避免同时访问多个成员等问题。总之,共用体是嵌入式编程中一种灵活且强大的工具。
八、参考文献
- 《C Primer Plus》:由 Stephen Prata 所著。
- 《C 和指针》:从基本数据类型讲起,涵盖控制结构、运算符、表达式、指针、数组、函数、内存管理等,内容全面且对初学者友好,也包含共用体相关知识。
- 《C 专家编程》:深入讲解 C 语言的高级特性和编程技巧,包括共用体在不同场景下的应用方式等内容,适合有一定 C 语言基础的读者提升编程能力。
- 《C 陷阱与缺陷》:从实践出发,讲解 C 语言中常见的陷阱和缺陷,其中可能涉及共用体使用过程中容易出现的问题及解决方案和技巧,适合有一定 C 语言编程经验的读者。
- 《嵌入式系统软件设计》:作者是 Michael J. Pont,介绍了嵌入式系统的基础知识和应用开发过程中的常见问题及解决方案。
相关文章:
嵌入式C语言:什么是共用体?
在嵌入式C语言编程中,共用体(Union)是一种特殊的数据结构,它允许在相同的内存位置存储不同类型的数据。意味着共用体中的所有成员共享同一块内存区域,因此,在任何给定时间,共用体只能有效地存储…...
QT简单实现验证码(字符)
0) 运行结果 1) 生成随机字符串 Qt主要通过QRandomGenerator类来生成随机数。在此之前的版本中,qrand()函数也常被使用,但从Qt 5.10起,推荐使用更现代化的QRandomGenerator类。 在头文件添加void generateRandomNumb…...
【4Day创客实践入门教程】Day2 探秘微控制器——单片机与MicroPython初步
Day2 探秘微控制器——单片机与MicroPython初步 目录 Day2 探秘微控制器——单片机与MicroPython初步MicroPython语言基础开始基础语法注释与输出变量模块与函数 单片机基础后记 Day0 创想启程——课程与项目预览Day1 工具箱构建——开发环境的构建Day2 探秘微控制器——单片机…...
C++中vector追加vector
在C中,如果你想将一个vector追加到另一个vector的后面,可以使用std::vector的成员函数insert或者std::copy,或者简单地使用std::vector的push_back方法逐个元素添加。这里我将展示几种常用的方法: 方法1:使用insert方…...
【Java高并发】基于任务类型创建不同的线程池
文章目录 一. 按照任务类型对线程池进行分类1. IO密集型任务的线程数2. CPU密集型任务的线程数3. 混合型任务的线程数 二. 线程数越多越好吗三. Redis 单线程的高效性 使用线程池的好处主要有以下三点: 降低资源消耗:线程是稀缺资源,如果无限…...
[论文阅读] (37)CCS21 DeepAID:基于深度学习的异常检测(解释)
祝大家新春快乐,蛇年吉祥! 《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正࿰…...
国内flutter环境部署(记录篇)
设置系统环境变量 export PUB_HOSTED_URLhttps://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URLhttps://storage.flutter-io.cn使用以下命令下载flutter镜像 git clone -b stable https://mirror.ghproxy.com/https://github.com/<github仓库地址>#例如flutter仓…...
Java面试题2025-并发编程进阶(线程池和并发容器类)
线程池 一、什么是线程池 为什么要使用线程池 在开发中,为了提升效率的操作,我们需要将一些业务采用多线程的方式去执行。 比如有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总就可…...
【算法应用】基于鲸鱼优化算法求解OTSU多阈值图像分割问题
目录 1.鲸鱼优化算法WOA 原理2.OTSU多阈值图像分割模型3.结果展示4.参考文献5.代码获取 1.鲸鱼优化算法WOA 原理 SCI二区|鲸鱼优化算法(WOA)原理及实现 2.OTSU多阈值图像分割模型 Otsu 算法(最大类间方差法)设灰度图像有 L L …...
设计模式的艺术-策略模式
行为型模式的名称、定义、学习难度和使用频率如下表所示: 1.如何理解策略模式 在策略模式中,可以定义一些独立的类来封装不同的算法,每个类封装一种具体的算法。在这里,每个封装算法的类都可以称之为一种策略(Strategy…...
Autogen_core源码:_agent_instantiation.py
目录 _agent_instantiation.py代码代码解释代码示例示例 1:使用 populate_context 正确设置上下文示例 2:尝试在上下文之外调用 current_runtime 和 current_agent_id示例 3:模拟 AgentRuntime 使用 AgentInstantiationContext _agent_instan…...
7. 马科维茨资产组合模型+金融研报AI长文本智能体(Qwen-Long)增强方案(理论+Python实战)
目录 0. 承前1. 深度金融研报准备2. 核心AI函数代码讲解2.1 函数概述2.2 输入参数2.3 主要流程2.4 异常处理2.5 清理工作2.7 get_ai_weights函数汇总 3. 汇总代码4. 反思4.1 不足之处4.2 提升思路 5. 启后 0. 承前 本篇博文是对前两篇文章,链接: 5. 马科维茨资产组…...
安装Maven(安装包+步骤)
1. 安装: 通过网盘分享的文件:apache-maven-3.9.9 链接: https://pan.baidu.com/s/16AE_brICuw6sS0tC6tmE1Q?pwda74r 提取码: a74r --来自百度网盘超级会员v3的分享 2.新建应该系统变量: 3.path中添加bin文件夹路径 4.建议在这里建一个仓库文件夹 博主的: 5.I…...
【云安全】云原生-K8S-搭建/安装/部署
一、准备3台虚拟机 务必保证3台是同样的操作系统! 1、我这里原有1台centos7,为了节省资源和效率,打算通过“创建链接克隆”2台出来 2、克隆之前,先看一下是否存在k8s相关组件,或者docker相关组件 3、卸载原有的docker …...
基于PLC的变频调速系统设计
摘要 现代科技发展迅速,特别是通讯技术的发展,工业现场提供了便捷的数据交互和控制的手段,将工业现场的仪表、驱动器、控制器以及上位机之间进行通讯连接,进行相互信息交互,数据准确高效的传送,并且对现场的…...
单细胞-第四节 多样本数据分析,下游画图
文件在单细胞\5_GC_py\1_single_cell\2_plots.Rmd 1.细胞数量条形图 rm(list ls()) library(Seurat) load("seu.obj.Rdata")dat as.data.frame(table(Idents(seu.obj))) dat$label paste(dat$Var1,dat$Freq,sep ":") head(dat) library(ggplot2) lib…...
【算法】动态规划专题① ——线性DP python
目录 引入简单实现稍加变形举一反三实战演练总结 引入 楼梯有个台阶,每次可以一步上1阶或2阶。一共有多少种不同的上楼方法? 怎么去思考? 假设就只有1个台阶,走法只有:1 只有2台阶: 11,2 只有3台…...
知识管理平台在数字经济时代推动企业智慧决策与知识赋能的路径分析
内容概要 在数字经济时代,知识管理平台被视为企业智慧决策与知识赋能的关键工具。其核心作用在于通过高效地整合、存储和分发企业内部的知识资源,促进信息的透明化与便捷化,使得决策者能够在瞬息万变的市场环境中迅速获取所需信息。这不仅提…...
LabVIEW微位移平台位移控制系统
本文介绍了基于LabVIEW的微位移平台位移控制系统的研究。通过设计一个闭环控制系统,针对微位移平台的通信驱动问题进行了解决,并提出了一种LabVIEW的应用方案,用于监控和控制微位移平台的位移,从而提高系统的精度和稳定性。 项目背…...
java求职学习day23
MySQL 单表 & 约束 & 事务 1. DQL操作单表 1.1 创建数据库,复制表 1) 创建一个新的数据库 db2 CREATE DATABASE db2 CHARACTER SET utf8; 2) 将 db1 数据库中的 emp 表 复制到当前 db2 数据库 1.2 排序 通过 ORDER BY 子句 , 可以将查询出的结果进行排序 ( 排序只…...
【张雪峰高考志愿填报】合集
【张雪峰高考志愿填报】合集 链接:https://pan.quark.cn/s/89a2d88fa807 高考结束,分数即将揭晓,志愿填报的关键时刻近在眼前!同学们,这可是人生的重要转折点,选对志愿,就像为未来铺就一条…...
22.Word:小张-经费联审核结算单❗【16】
目录 NO1.2 NO3.4 NO5.6.7 NO8邮件合并 MS搜狗输入法 NO1.2 用ms打开文件,而不是wps❗不然后面都没分布局→页面设置→页面大小→页面方向→上下左右:页边距→页码范围:多页:拼页光标处于→布局→分隔符:分节符…...
MYSQL 商城系统设计 商品数据表的设计 商品 商品类别 商品选项卡 多表查询
介绍 在开发商品模块时,通常使用分表的方式进行查询以及关联。在通过表连接的方式进行查询。每个商品都有不同的分类,每个不同分类下面都有商品规格可以选择,每个商品分类对应商品规格都有自己的价格和库存。在实际的开发中应该给这些表进行…...
python3+TensorFlow 2.x(二) 回归模型
目录 回归算法 1、线性回归 (Linear Regression) 一元线性回归举例 2、非线性回归 3、回归分类 回归算法 回归算法用于预测连续的数值输出。回归分析的目标是建立一个模型,以便根据输入特征预测目标变量,在使用 TensorFlow 2.x 实现线性回归模型时&…...
cpp智能指针
普通指针的不足 new和new[]的内存需要用delete和deletel]释放。 程序员的主观失误,忘了或漏了释放。 程序员也不确定何时释放。 普通指针的释放 类内的指针,在析构函数中释放。 C内置数据类型,如何释放? new出来的类,本身如…...
Android --- CameraX讲解
预备知识 surface surfaceView SurfaceHolder surface 是什么? 一句话来说: surface是一块用于填充图像数据的内存。 surfaceView 是什么? 它是一个显示surface 的View。 在app中仍在 ViewHierachy 中,但在wms 中可以理解为…...
CentOS7非root用户离线安装Docker及常见问题总结、各种操作系统docker桌面程序下载地址
环境说明 1、安装用户有sudo权限 2、本文讲docker组件安装,不是桌面程序安装 3、本文讲离线安装,不是在线安装 4、目标机器是内网机器,与外部网络不连通 下载 1、下载离线安装包,并上传到$HOME/basic-tool 目录 下载地址&am…...
前端面试笔试题目(一)
以下模拟了大厂前端面试流程,并给出了涵盖HTML、CSS、JavaScript等基础和进阶知识的前端笔试题目,以帮助你更好地准备面试。 面试流程模拟 1. 自我介绍(5 - 10分钟):面试官会请你进行简单的自我介绍,包括…...
笔记本搭配显示器
笔记本:2022款拯救者Y9000P,显卡RTX3060,分辨率2560*1600,刷新率:165Hz,无DP1.4口 显示器:2024款R27Q,27存,分辨率2560*1600,刷新率:165Hz &…...
设计转换Apache Hive的HQL语句为Snowflake SQL语句的Python程序方法
首先,根据以下各类HQL语句的基本实例和官方文档记录的这些命令语句各种参数设置,得到各种HQL语句的完整实例,然后在Snowflake的官方文档找到它们对应的Snowflake SQL语句,建立起对应的关系表。在这个过程中要注意HQL语句和Snowfla…...
