你今天volatile了吗?--慎重使用

嵌入式C语言中 `volatile` 的作用你真的懂吗? 阅读详情
  根据c/c++语法,const可以出现的地方,volatile几乎也都可以出现。但是,const修饰的对象其值不能改变,而volatile修饰的对象其值可以随意地改变,也就是说,volatile对象值可能会改变,即使没有任何代码去改变它。在这一点上,最典型的例子就是内存映射的设备寄存器和多线程中的共享对象。懂得使用volatile也是一门小小的艺术。 使用volatile约束符可以阻止编译器对代码过分优化防止出现一些你意想不到的情况,达不到预期的结果;过频地使用volatile很可能会增加代码尺寸和降低性能。下面举个例子来说明volatile在优化中的微妙作用。

1.阻止编译器优化
  ARM Evaluator-7T模拟单机板使用基于内存映射的设备寄存器叫特殊寄存器,用来
控制和交互外围设备。CPU对内存的操作可以做到按位进行,而特殊寄存器是4字节对齐并占四个字节。你可以象unsigned int变量一样操作特殊寄存器(有些人可能更喜欢uint32_t,认为这样体现寄存器占用4个字节的特点。uint32_t在C99 头文件<stdint.h>中有定义)。而这里,为了体现寄存器本身作为寄存器的含义而非它的物理意义的,我们做如下定义:
typedef uint32_t special_register;

  Evaluator-7T板子上有一个按钮(可以认为是外设之一)。按下该按钮可以对IOPDATA寄存器第8位置1,相反,释放按钮会将该位重新清0。我们使用枚举方法为IOPDATA寄存器的第8位置定义一个掩码mask:
enum { button = 0x100 };

IOPDATA寄存器对应的地址为0x3FF5008,我们可以用宏形象地定义IOPDATA:

#define IOPDATA (*(special_register *)0x03FF5008)

有了这个定义,我们执行下面的循环就可以使CPU一直等待该按钮被按下:
while ((IOPDATA & button) == 0)
    ;

  然而这个期望必须建立在编译器不对代码进行优化的前提假设之上。如果编译器优化这段代码,那么它会认为在这个循环中没有什么会改变IOPDATA而且认为条件判断结果总是真或假,最终优化的结果是只对(IOPDATA & button)==0判断一次,之后的循环都不在对其进行判断,其等同于:
if ((IOPDATA & button) == 0)
    for (;;)
        ;

  显然,如果条件判断结果为真(那么之后都会认为是真),那么这段代码将会陷入死循环。如果判断为假,那么循环就此结束。可以看出,优化的代码效率更高,因为每次循环相比原来的执行时间要短。不幸的是,这段优化代码使得它根本就不能响应按钮的每次动作。那么,如何解决这个问题呢?解决的关键就是不要让编译器优化这段代码,使用volatile就可以办到这一点。我们修改前面关于IOPDATA的宏定义:
#define IOPDATA (*(special_register volatile *)0x03FF5008)

这个定义将IOPDATA 定义为volatile类型的寄存器。volatile隐含地告诉编译器特殊寄存器可能会改变内容,即使没有任何显式地代码去改变它的内容。这样一来,编译器就不对IOPDATA作优化,而是每次都去访问IOPDATA,这其实正是我们所期望的。

2.无意中降低了效率
  有时候,如果不注意的话,使用volatile会无意中降低代码效率。举个例子。Evaluator-7T有一个七段数码显示器见下图:

          

  在IOPDATA 寄存器中第10到16位用来控制显示器的每一段。比如第10位就是用来控制顶部的那段显示,置1则点亮它,清0则熄灭它。我们可以定义一个掩码mask来覆盖从第10到16的所有位:
enum { display = 0x1FC00 };
假设变量b用来控制这7段显示器的每一段显示,并且b的值已经你想要设置值(准备用来显示哪几段和熄灭哪几段,其它无关的位均为0)。那么你想要改变设置新的显示方式的操作就是:
IOPDATA = b;
但是这种赋值可能会改变第10到16位之外的其它位,这是我们不期望的。所以,采用下面的方法更好:
IOPDATA |= b
但是,使用 |= 并不能熄灭那些已经点亮的显示段(1 | 0 -> 1),所以我们可以用下面的函数达到目的:
void display_put(uint32_t b)
{
    IOPDATA &= ~display;    /*熄灭所有的段*/
    IOPDATA |= b;        /*点亮想要的段*/
}

  不过,可能没想到的是这样的操作在无意中降低了代码效率。因为我们定义IOPDATA为
volatile类型,它阻止了编译器对代码的优化,要求任何读写IOPDATA的操作都死死板板地进行。IOPDATA &= ~display的等价表现为IOPDATA = IOPDATA & ~display,也就是先从IOPDATA读出内容然后与上~display,最后又回写IOPDATA。同理,IOPDATA |=b也有相似的过程。整个过程分别有2次读IOPDATA和2次写IOPDATA的操作。如果IOPDATA不使用volatile,那么编译器会要求将IOPDATA & ~display的结果放在CPU寄存器中,直到完成IOPDATA |= b操作才写回特殊寄存器IOPDATA。显然后者较之前者分别省掉了1次读IOPDATA和1次I写OPDATA的耗时操作(外设操作是最耗时的),效率要高很多。如果你想使用volatile但又能使能优化功能,你可以将函数作如下的修改:

void display_put(uint32_t b)
{
    register uint32_t temp = IOPDATA;/*定义局部变量*/
    temp &= ~display;         /*读取IOPDATA内容到temp*/
    temp |= b;              /*将temp内容或上b*/
    IOPDATA = temp;          /*将结果写回IOPDATA*/
}

这样做有点烦琐,下面的等效方法更简单:
void display_put(uint32_t b)
{
    IOPDATA = (IOPDATA & ~display) | b;
}

结论:从该例子看出,它并不鼓励使用volatile,即使要用也要很小心,因为volatile可能在无意中降低了代码效率,而你却无法察觉。但是,我们说,不鼓励并不是说就不能或不要用,而是要懂得何时用,怎么用好它。其所谓智用了。

 
Unity单例模式实战:从基础实现到高级架构与性能优化 在软件架构中,设计模式是解决常见问题的可复用方案,其中单例模式确保一个类仅有一个实例并提供全局访问点,其核心原理在于通过私有构造函数和静态成员控制实例化过程。这一模式的技术价值在于统一管理全局状态和资源,有效降低模块间的耦合度,提升代码的可维护性。在游戏开发,特别是基于Unity引擎的项目中,单例模式广泛应用于游戏管理器、音频系统、资源加载器等需要全局唯一访问点的场景。本文聚焦于Unity环境下单例模式的工程实践,深入探讨了其基础实现、线程安全优化、泛型封装等高级应用,并针对脚本执行顺序、跨场景实例重复等典 阅读详情

相关推荐

深入解析RA8D2 MCU CPU控制寄存器:多核启动、TrustZone安全与调试实践

在嵌入式系统开发中,CPU控制寄存器是实现多核协同、安全启动和系统调试的核心硬件机制。其原理是通过配置特定寄存器位,精确控制CPU核心的激活时序、复位行为、中断向量以及安全状态迁移。这项技术的核心价值在于为汽车网关、工业控制器等高可靠性应用提供确定性的硬件控制基础,确保系统从启动到运行的每个环节都安全可控。在应用场景上,它支撑了基于Arm TrustZone-M的安全启动流程、多核间有序初始化以及基于CoreSight的复杂调试系统搭建。本文以瑞萨RA8D1/M33双核MCU为例,详解其**CPU控制寄存器

weixin_34221112的博客 355

嵌入式C编程个人笔记

关键字volatile有什么含意?并举出三个不同的例子?存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改。,它会告诉编译器不要对存储在这个地址的数据进行假设。volatile提醒编译器,它后面所定义的变量随时都有可能改变。因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

hllposion的博客 970

DDR、LPDDR、eMMC、NAND与NOR Flash:五大存储技术核心解析与实战选型

在计算机与嵌入式系统中,存储技术是决定设备性能与体验的核心基础。其核心原理围绕易失性与非易失性展开:易失性存储如RAM,依赖持续供电实现高速数据存取,是CPU的‘工作台’;非易失性存储如闪存,断电后数据不丢失,扮演‘档案柜’角色。从技术价值看,这直接关系到系统的启动速度、运行流畅度与数据可靠性。在应用场景上,从智能手机、个人电脑到物联网设备,不同的存储组合方案深刻影响着产品的成本、功耗与性能表现。本文聚焦于DDR/LPDDR内存、eMMC嵌入式存储以及NAND/NOR闪存这五大关键技术,深入剖析它们的工作原

weixin_30691871的博客 407

C语言volatile关键字

在学习setjmp和longjmp函数时涉及到了volatile关键字。volatile关键字是什么作用?为什么要设置该关键字? 转自: http://blog.youkuaiyun.com/tigerjibo/article/details/7427366 一.前言 1.编译器优化介绍:         硬件级别优化:由于内存访问速度远不及CPU处理速度,为提高机器整

Sunshine的专栏 1429

MC9S12Q128核心外设深度解析:定时器、电源与Flash实战指南

在嵌入式系统开发中,微控制器(MCU)的外设模块是实现复杂功能的关键。理解其工作原理,需要从基础概念入手:定时器模块负责精准的时序控制与事件管理,其**门控时间累加模式**可用于精确测量外部脉冲宽度;电源管理模块(如电压调节器)则为系统提供稳定供电,其**低电压检测(LVD)** 功能是实现电源监控与系统保护的重要机制。这些技术的核心价值在于提升系统的可靠性、实时性与能效。在汽车电子、工业控制等应用场景中,稳健的外设驱动是保障产品长期稳定运行的基础。本文聚焦于MC9S12Q128的三大核心模块,深入探讨其配

GOBY 栏目 402

你今天volatile了吗?--准确定位

在前一篇关于volatile文章《你今天volatile了吗?--慎重使用》(在后面,简称《慎重使用》)中提到,volatile定义的对象其内容可能会忽然的变化。换句话讲,如果你定义了一个volatile对象,就等于你告诉编译器该对象的内容可能会改变,即使代码中没有任何语句去改变该对象。编译器访问非volatile对象和volatile对象的方式很不一样。对于前者(经优化后),它先将非volati

xjbclz的专栏 272

嵌入式C语言volatile关键字作用

本文主要介绍嵌入式C语言volatile关键字作用,volatile 是一个类型修饰符(Type Qualifier),用于告知编译器:该变量的值可能被程序以外的因素意外修改,因此编译器不应对此变量进行激进的优化。其在嵌入式程序设计中有着非常重要的作用,本文对该关键字做了详细的介绍。

mftang的博客 1261

Amesim与Simulink联合仿真:S-Function接口配置与实战避坑指南

本文详细介绍了Amesim与Simulink联合仿真中S-Function接口的配置方法与实战避坑指南。从环境搭建、编译器配置到S-Function接口实战,逐步解析关键步骤与常见问题解决方案,帮助工程师高效实现多领域系统仿真,特别适合汽车、航空航天等复杂系统建模场景。

weixin_29173777的博客 343

嵌入式开发必知:volatile关键字的妙用

本文深入解析了C语言中volatile关键字的核心作用和应用场景。volatile告知编译器变量值可能被外部因素(如硬件、中断、多线程)改变,强制每次访问都直接从内存读取/写入。主要应用于:1)硬件寄存器访问(确保实时读取硬件状态);2)中断服务程序共享变量(保证主程序可见中断修改);3)多线程共享变量(需配合同步机制)。关键特性包括禁用优化但不保证原子性,需注意避免常见误区,如误作同步工具或忽略原子性要求。在嵌入式开发中,正确使用volatile是确保硬件交互可靠性的关键。

张佳航的博客 985

VisDrone2019数据集实战:从下载到YOLO格式转换的完整指南

本文提供了一份完整的VisDrone2019数据集实战指南,详细介绍了从数据集下载、理解其独特的标注格式,到转换为YOLO模型可直接训练格式的全过程。通过清晰的代码示例和步骤讲解,帮助读者掌握处理复杂无人机视觉数据集的核心技能,为基于YOLO的目标检测模型训练做好准备。

weixin_29165725的博客 384

QT+QGIS跨平台编译(基于Qt,采用一套代码、一套框架,完成QGIS第三方库、核心库、应用程序的跨平台编译)

QT+QGIS跨平台编译。 基于Qt,采用一套代码、一套框架,完成QGIS第三方库、核心库、应用程序的跨平台编译。

LuckyHanMo的博客 4373

[多线程]线程安全问题再讨论 - volatile

原来这是因为编译器为了执行效率,会进行优化,它检测到这个数字一直没有变化.本来的while循环步骤应该是直接在内存中读取a的值,然后在判断是否进行打印.但是为了效率,它优化的时候,发现这个数字并没有什么变化.就省去了去内存中读取这个步骤.直接从寄存器(工作内存)中读取数值a,此时我们在内存中修改这个数字,自然就影响不了结果了.为了举这个例子,我们先介绍一种设计模式-单例模式中的懒汉模式.单例模式是一种设计模式,它通过技巧,让一个类只能有一个对象,如果在实例化这个类,企图创建一个新的的对象就会报错.

weixin_64173948的博客 400

[AI] 本地部署 Dify 遇到 Internal Error?一文彻底解决密码不一致引发的问题

摘要:Dify本地部署常见InternalError错误主要由配置残留或密码不一致导致,特别是修改.env文件后未同步清除数据库记录。彻底解决方案包括:1)清理Docker数据;2)删除旧配置和数据库;3)还原源代码;4)重建.env文件;5)重新初始化部署。关键提醒:记录部署路径、备份.env文件并妥善保存密码。该错误本质是配置冲突,最有效方法是完全重置而非调试,保持清晰的部署管理可避免多数InternalError问题。(149字)

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 432

AtomicInteger

目录 一、从a++说起为什么使用AtomicInteger 二、原理分析 三、总结 java并发包里面的类一直是学习和面试的重点,这篇文章主要是对java并发包的其中一个类AtomicInteger的讲解。从为什么要出现AtomicInteger再到其底层原理来一个分析。 一、从a++说起为什么使用AtomicInteger 我们知道java并发机制中主要有三个特性需要我们去考虑,原子性、可见性和有序性。synchronized关键字可以保证可见性和有序性却无法保证原子性。而这个Atomi

半桶水的博客 7702

Android 11.0 app添加校验锁(输入密码才能进入app)

11.0定制化开发中客户要求给app添加锁,就是点击app 图标时,会弹出Dialog,需要输入密码才能进入app中,就是应用校验锁, 最开始想到在Launcher3中实现,但是如果更换了默认Launcher 又不行了 所以最终还是得拦截app启动Activity 从这里入手了 Launcher与APP是在两个不同的进程中,他们之间的通信是通过Binder完成的,点击Launcher上的某个APP,这时会调用Launcher的startActivitySafely方法。 public boolean sta

安卓兼职framework和app工程师的博客 1952

FPGA经验谈系列文章——3、逻辑级数

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 Xilinx FPGA开发tips系列——前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 一、pandas是什么? 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的

gzy0506的博客 1万+

atomicinteger_详解java并发原子类AtomicInteger(基于jdk1.8源码分析)

java并发包里面的类一直是学习和面试的重点,这篇文章主要是对java并发包的其中一个类AtomicInteger的讲解。从为什么要出现AtomicInteger再到其底层原理来一个分析。一、从a++说起为什么使用AtomicInteger我们知道java并发机制中主要有三个特性需要我们去考虑,原子性、可见性和有序性。volatile关键字可以保证可见性和有序性却无法保证原子性。而这个Atomic...

weixin_39691968的博客 302

30天Python入门(第一天:Python入门)

30天带你入门Python,这是第一天

Science prince的博客 582

浅谈操作系统中的重要概念——线程(3)——设计模式

设计模式就相当于菜谱,有了菜谱/秘籍,就能够根据菜谱上的指引/步骤做出许多从前不会的美食,就算不会下厨的人,拥有了食谱,他的厨艺也能够得到提升和保障。因此设计模式就是程序员的菜谱,设计模式中介绍了许多典型场景,以及针对这些典型场景的处理办法。按照设计模式来写的代码不会很差,在一定的规范范围里。设计模式有很多种,不止23种,今天主要介绍两种常见、常用的:1、单例模式2、工厂模式工厂,顾名思义是用来生产的。那么对应到我们代码上,工厂模式就是用来生产对象的。那么具体是怎么进行生产对象的呢??

m0_63191002的博客 1119

【unity实战】Unity手搓脚本工具实现合并网格功能

本文介绍了在Unity中优化网格性能的方法,通过合并重复网格减少绘制调用。主要使用Mesh.CombineMeshes方法实现网格合并,支持统一材质和多材质处理。核心内容包括:1)基础合并脚本实现,将子网格合并到父对象并禁用原网格;2)统一材质设置,支持自定义或默认材质;3)多材质合并方案,通过设置mergeSubMeshes=false保留子网格材质索引。文章还提供了完整代码示例和操作演示,适用于实际项目中的网格优化需求。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 556

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

RA8P1选项设置内存深度解析:安全启动、看门狗与硬件保护实战

在嵌入式系统开发中,微控制器的启动配置与硬件保护机制是构建可靠、安全系统的基石。其核心原理在于通过芯片内部特殊的非易失性存储区域(如选项设置内存),在系统上电之初即锁定关键硬件行为,如时钟源、看门狗、电压检测及代码保护等。这种硬件级配置为系统提供了固化的“基因”,能有效防止软件跑飞、确保电源稳定,并是实现安全启动与防篡改的技术基础。其技术价值在于将关键安全策略从软件层面剥离,交由不可篡改的硬件执行,极大提升了系统的确定性与抗攻击能力。该机制广泛应用于对功能安全和信息安全有严苛要求的场景,如物联网网关、工业控

chubaisheng8627的博客 348

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

池化技术深度解析:从核心原理到高并发系统优化实践

池化技术是一种通过复用资源来提升系统性能与稳定性的核心设计模式,其核心原理在于预先创建并维护一组可重用资源实例,形成资源池供应用按需取用,从而避免频繁创建销毁带来的性能开销。这一技术通过空间换时间的策略,有效降低了资源管理成本,提升了响应速度,并实现了对系统资源的精细化控制。在技术价值层面,池化技术不仅是高并发系统的性能基石,更是保障服务稳定性的关键手段,能够有效防止因资源耗尽导致的系统雪崩。其应用场景广泛覆盖数据库连接管理、线程调度、对象复用等多个领域,尤其在微服务架构和云原生环境中,池化技术与弹性伸缩、

weixin_33924770的博客 487

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

MC68HC908GZ内存架构与FLASH编程实战指南

在嵌入式系统开发中,微控制器(MCU)的内存架构是理解硬件资源分配与程序执行的基础。其核心原理在于通过统一的地址空间,将程序存储器、数据存储器和外设寄存器映射到不同区域,CPU通过寻址访问各类资源。这种设计的技术价值在于简化了CPU指令集,但要求开发者精确掌握内存映射,以避免非法访问导致系统故障。在工程实践中,FLASH存储器的在应用编程(IAP)能力尤为关键,它支持固件远程升级,广泛应用于汽车电子、工业控制等场景。本文以MC68HC908GZ系列为例,深入解析其包括用户FLASH、RAM和I/O寄存器的内

weixin_30673611的博客 402
上一篇: 你今天volatile了吗?--准确定位
下一篇: 嵌入式OS你用了吗
embeddedsoft
博客等级 码龄20年 51粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值