C中的“volatile”限定符

1、概述

尽管有大量C语言的文献,但是 “volatile” 关键字在某种程度上还是不能被很好地理解(甚至是有经验的C程序员)。究其原因,是在用高级语言编写的典型C程序中,没有 “volatile” 变量的真实用例。基本上,除非你用C语言进行一些低级硬件编程,否则你可能不会使用被限定为 “volatile” 的变量。所谓低级编程,值得是一段C代码,它处理外围设备、IO端口(主要是内存映射的IO端口)、与硬件交互的终端服务例程(ISR)。这就是为什么很难有一个简单的C程序能直接地展示 “volatile” 关键字的确切效果。

事实上,本文中,如果我们能够解释 “volatile” 的含义和目的,这将为进一步研究和使用C中的 “volatile” 奠定基础。要理解 “volatile”,首先需要了解编译器对C程序的作用。在高层,我们知道编译器将C代码转换为机器代码,这样可执行文件就可以在没有实际源代码的情况下运行。与其他技术类似,编译器技术也有很大的发展。在将源代码转换为机器代码时,编译器通常会尝试优化输出,以便最终执行较少的机器代码。这样的一个优化是删除访问变量的不必要的机器代码,从编译器的角度来看,这些代码不会改变。假设有如下的代码:

uint32 status = 0;while (status == 0)
{/*Let us assume that status isn't being changed in this while loop or may be in our whole program*//*So long as status (which could be reflecting status of some IO port) is ZERO, do something*/
}

优化编译器会发现 while 循环中并不会修改 status。因此,不需要在每次循环迭代后一次又一次地访问 status 变量。因此编译器会将这个循环转换为一个无限循环,即 while(1),这样读取 status 的机器代码就不需要了。请注意,编译器不知道 status 是一个特殊的变量,它可以在任何时间点从当前程序外部进行更改,例如,在外围设备上发生了一些IO操作,设备IO端口被内存映射到此变量。所以,实际上,我们希望编译器在每次循环迭代后访问 status 变量,即使它没有被编译器正在编译的程序修改。

有人可能会说,我们可以关闭此类程序的所有编译器优化,这样我们就不会遇到这种情况。这不是一种好的方法,因为诸多原因,例如

A)每个编译器实现都是不同的,因此它不是一个可移植的解决方案
B) 仅仅因为一个变量,我们不想关闭编译器在程序的其他部分所做的所有其他优化
C)关闭所有优化,我们的低级别程序无法按预期工作,例如程序大小(size)增加过多或延迟执行

这就是 “volatile” 存在的意义。基本上,我们需要告诉编译器 status 是特殊变量,不允许对该变量进行优化。有了这个,我们可以这样定义我们的变量:

volatile uint32 status = 0;

为了解释简单,选择上面的例子。但通常,volatile 用于指针,如下所示:

volatile uint32 * statusPtr = 0xF1230000

这里,statusPtr 指向一个内存位置(如一些IO端口),其中的内容可以在任何时间点从一些外围设备更改。请注意,我们的程序可能无法控制或知道内存何时会改变。所以将其设为 “volatile”,这样编译器就不会对 statusPtr 指向的 可变 变量进行优化了。

在我们讨论 “volatile” 的上下文中,引用了C语言标准即ISO/IEC 9899 C11 - 第6.7.3条

“An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects.”

“A volatile declaration may be used to describe an object corresponding to a memory-mapped input/output port or an object accessed by an asynchronously interrupting function. Actions on objects so declared shall not be ‘‘optimized out’’ by an implementation or reordered except as permitted by the rules for evaluating expressions.”

简单地说,C标准说 “volatile” 变量能从程序的外部更改,这就是为什么编译器不应该优化它们的访问。现在,你可以猜测,程序中有太多的 “volatile” 变量也会导致编译器的优化越来越少。我们希望它给你足够的背景知识,让你了解 ”volatile“ 的含义和目的。

从本文中,希望您去掉 “volatile变量–>不要对该变量进行编译器优化” 的概念!

2、实例

volatile 关键字的目的是防止编译器以编译器无法确定的方式对可能发生变化的对象进行任何优化。

被声明为 volatile 的对象在优化中被省略,因为它们的值可以随时被当前代码范围之外的代码更改。系统总是从内存位置读取 volatile 对象的当前值,而不是在请求时将其值保存到临时寄存器中,即使先前的指令曾向同一个对象请求过该值。因为,简单的问题是,变量的值是如何以编译器无法预测的方式变化?考虑以下情况来回答这个问题:

1)全局变量被范围外的终端服务例程修改:例如,全局变量可以表示将动态更新的数据端口(通常是全局指针,称为内存映射IO)。读取数据端口的代码必须声明为volatile,以便获取端口上可用的最新数据。如果变量未能声明为volatile,那么将导致编译器优化代码,使其只读取端口一次,并在临时寄存器中使用相同的值来加快程序速度(速度优化)。通常,当新数据可用而出现中断时,ISR用于更新这些数据端口。

2)多线程应用程序中的全局变量:线程的通信有多种方式,即消息传递、共享内存、邮箱等。全局变量是共享内存的弱形式。当两个线程通过全局变量共享信息时,这些变量需要用 volatile 进行限定。由于线程是异步执行的,因此由一个线程引起的全局变量的任何更新都应该由另一个使用者线程立即获取。编译器可以读取全局变量,并将它们放置在当前线程上下文的临时变量中。为了消除编译器优化的效果,需要将此类全局变量限定为volatile。

如果不使用 volatile 限定符,就会产生下面的问题:

1)启用优化后,代码可能无法按预期执行
2)启用和使用中断时,代码可能无法按预期执行

来看个例子了解编译器如何解释 volatile 关键字。考虑下面的代码。我们正在使用指针修改const对象的值,且正在编译没有优化选项的代码。因此,编译器不会进行任何优化,并且会修改 const 对象的值。

/* Compile code without optimization option */
#include <stdio.h>
int main(void)
{const int local = 10;int *ptr = (int*) &local;printf("Initial value of local : %d \n", local);*ptr = 100;printf("Modified value of local: %d \n", local);return 0;
}

当我们使用 gcc 的 “–save temps” 选项编译代码时,它会生成 3 个输出文件:

1)预处理代码(有 .i 扩展名)
2)汇编代码 (有 .s 扩展名)
3)目标代码(有 .o 扩展名)

在没有优化的情况下编译代码,这就是为什么汇编代码的大小会更大。

输出:

[narendra@ubuntu]$ gcc volatile.c -o volatile –save-temps
[narendra@ubuntu]$ ./volatile
Initial value of local : 10
Modified value of local: 100
[narendra@ubuntu]$ ls -l volatile.s
-rw-r–r– 1 narendra narendra 731 2016-11-19 16:19 volatile.s
[narendra@ubuntu]$

使用优化选项(即 -O 选项)编译相同的代码。如下代码中,“local” 被声明为 const(非volatile)。GCC编译器进行优化并忽略试图更改const 对象值的指令。因此,const对象的值保持不变。

/* Compile code with optimization option */
#include <stdio.h>int main(void)
{const int local = 10;int *ptr = (int*) &local;printf("Initial value of local : %d \n", local);*ptr = 100;printf("Modified value of local: %d \n", local);return 0;
}

对于上面的代码,编译器会进行优化,这就是汇编代码的大小会减少的原因。

输出:

[narendra@ubuntu]$ gcc -O3 volatile.c -o volatile –save-temps
[narendra@ubuntu]$ ./volatile
Initial value of local : 10
Modified value of local: 10
[narendra@ubuntu]$ ls -l volatile.s
-rw-r–r– 1 narendra narendra 626 2016-11-19 16:21 volatile.s

将 const 对象声明为 volatile 并用优化选项编译代码。尽管编译代码时使用了优化选项,但是const对象的值依然会改变,因为变量被声明为了 volatile,这意味着不做任何优化。

/* Compile code with optimization option */
#include <stdio.h>int main(void)
{const volatile int local = 10;int *ptr = (int*) &local;printf("Initial value of local : %d \n", local);*ptr = 100;printf("Modified value of local: %d \n", local);return 0;
}

输出:

[narendra@ubuntu]$ gcc -O3 volatile.c -o volatile –save-temp
[narendra@ubuntu]$ ./volatile
Initial value of local : 10
Modified value of local: 100
[narendra@ubuntu]$ ls -l volatile.s
-rw-r–r– 1 narendra narendra 711 2016-11-19 16:22 volatile.s
[narendra@ubuntu]$

上面的例子可能不是一个很好的实际例子,但其目的是解释编译器如何解释 volatile 关键字。作为一个实际的例子,想想手机上的触摸传感器。提取触摸传感器的驱动程序将读取触摸的位置并将其发送到更高级别的应用程序。驱动程序本身不应修改(const-ness)读取位置,并确保每次读取触摸输入时都是新的(volatile-ness)。这样的驱动器必须以 const volatile 的方式读取触摸传感器输入。

注意:以上代码是特定于编译器的,可能不适用于所有编译器。这些例子的目的是让读者理解这个概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/577557.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

创建一台可以安装linux系统的虚拟机的流程

1、打开vmware-->点击左上角文件-->新建虚拟机-->自定义 2、默认选择&#xff0c;直接下一步 3、选中稍后安装操作系统&#xff0c;然后下一步 4、选中Linux&#xff0c;然后下拉框选择CentOS7(64位) 5、设置虚拟机名称及存储位置 6、设置虚拟机处理器数量及核心数 7、…

选择排序之C++实现

描述 选择排序&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法。它的基本思想是&#xff1a;每一轮从待排序的数据中选择最小&#xff08;或最大&#xff09;的一个元素&#xff0c;然后与待排序数据的第一个元素交换位置。对剩余未排序的数据重复这个过程&a…

Python与ArcGIS系列(十七)GDAL之shp转geojson

目录 0 简述1 Shapefile (SHP) 格式2 GeoJSON 格式3 代码实现0 简述 Shp格式是GIS中非常重要的数据格式,主要在Arcgis中使用,但在进行很多基于网页的空间数据可视化时,通常只接受GeoJSON格式的数据,众所周知JSON是利用键值对+嵌套来表示数据的一种格式,以其轻量、易解析的…

测试:JSON JSON5

JSON JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它基于JavaScript编程语言的一个子集。JSON是用于数据传输的一种格式&#xff0c;它易于人阅读和编写&#xff0c;同时也易于机器解析和生成。JSON格式由两部分组成&#…

MySQL_16.数据库事务相关概念

1.数据库事务 是指作为单个逻辑工作单元执行的系列操作,要么完全执行,要么完全不执行 2.事务的属性 (1)原子性 Atomicity: 原子性是指事务是一个不可分割的工作单位&#xff0c;事务中的操作要么都发生&#xff0c;要么都不发生 (2)一致性 Consistency: 事务前后数据的完…

【数据库设计和SQL基础语法】--事务和并发控制--事务的概念和特性

一、SQL事务基础 在数据库管理系统&#xff08;DBMS&#xff09;中&#xff0c;事务是指一个或一组数据库操作的执行单元&#xff0c;它被视为一个不可分割的工作单位。事务的目的是要确保数据库的完整性和一致性&#xff0c;即使在发生故障或错误的情况下也能保持数据的一致性…

【【IIC模块Verilog实现---用IIC协议从FPGA端读取E2PROM】】

IIC模块Verilog实现–用IIC协议从FPGA端读取E2PROM 下面是 design 设计 I2C_dri.v module IIC_CONTROL #(parameter SLAVE_ADDR 7b1010000 , // E2PROM 从机地址parameter CLK_FREQ 26d50_000_000 , // 50MHz 的时钟频率parameter …

《负责任研究行为规范指引(2023)》发布:引领科学研究的道德与规范

《负责任研究行为规范指引&#xff08;2023&#xff09;》发布&#xff1a;引领科学研究的道德与规范 公众号回复关键词&#xff1a;道德规范 获取《负责任研究行为规范指引&#xff08;2023&#xff09;》原文。 在科技迅速发展的当下&#xff0c;负责任的科学研究行为对于推…

Ensp dhcp全局地址池(配置命令 + 实例)

使用DHCP的好处&#xff1a;减少管理员的工作量、避免输入错误的可能、避免ip冲突 DHCP报文类型&#xff1a; DHCP DISCOVER:客户端用来寻找DHCP服务器 DHCP OFFER:DHCP服务器用来响应DHCP DISCOVER报文&#xff0c;此报文携带了各种配置信息 DHCP REQUEST:客户端配置请求确…

Python机器学习 – 用最小二乘法实现散点图

Python机器学习 – 用最小二乘法实现散点图 Machine Learning in Python – Implement Scatter Plot with Least Squares By JacksonML 1. 最小二乘法定义 最小二乘法是由A.M.Legendre&#xff08;勒让德&#xff09;先生最早提出的。他在1805年&#xff0c;通过《计算彗星轨…

k8s中的pod及创建pod的方式

1. 什么是pod? 在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c;Pod 是最小的可部署单元&#xff0c;它是容器的一种抽象层级。通俗地说&#xff0c;Pod 就像是一个运行在 Kubernetes 上的应用程序实例&#xff0c;但实际上&#xff0c;Pod 有一些特殊之处。 让我们…

3. 结构型模式 - 组合模式

亦称&#xff1a; 对象树、Object Tree、Composite 意图 组合模式是一种结构型设计模式&#xff0c; 你可以使用它将对象组合成树状结构&#xff0c; 并且能像使用独立对象一样使用它们 问题 如果应用的核心模型能用树状结构表示&#xff0c; 在应用中使用组合模式才有价值。 …

ISP 状态机轮转和bubble恢复机制学习笔记

1 ISP的中断类型 ISP中断类型 SOF: 一帧图像数据开始传输 EOF: 一帧图像数据传输完成 REG_UPDATE: ISP寄存器更新完成(每个reg group都有独立的这个中断) EPOCH: ISP某一行结尾(默认20)就会产生此中断 BUFFER DONE: 一帧图像数据ISP完全写到DDR了 2 ISP驱动状态机 通过camer…

三菱PLC开关量防抖滤波功能块

开关量防抖滤波功能块梯形图和SCL代码请参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/134936233https://rxxw-control.blog.csdn.net/article/details/134936233三菱PLC防抖滤波的另一种写法如下 https://rxxw-control.blog.csdn.net/article/det…

不同领域环境中的“组分分析”

组分分析在不同的学术领域和应用场景中可能有不同的含义&#xff0c;但通常它指的是一种分析方法&#xff0c;用于识别和量化一个复杂系统、样本或信号中的各个组成部分。 1. **化学组分分析**&#xff1a; 在化学领域&#xff0c;组分分析通常指的是识别和量化化学样品中各…

用CHAT了解更多知识点

问CHAT&#xff1a;什么是硅基生命和碳基生命&#xff1f; CHAT回复&#xff1a;硅基生命和碳基生命是两种理论性的生物体类型&#xff0c;这些生物体主要是由硅或碳元素以及其他元素构成的。 碳基生命是我们当前所熟知的生命形式。碳元素能够形成稳定且复杂的分子&#xff0c;…

推荐几款非常好用的软件,干货满满!

作为一个工具控&#xff0c;一直在社区索取别人的营养&#xff0c;今天在下将我搜集的一些应用贡献出来&#xff0c;推介十几个我常用的软件。一些是其他人反复推介确实经典&#xff0c;另一些是我偶然发现但经过使用感觉非常好用&#xff0c;一并献上&#xff0c;大家可以根据…

node封装一个图片拼接插件

说在前面 平时我们拼接图片的时候一般都要通过ps或者其他图片处理工具来进行处理合成&#xff0c;这次有个需求就需要进行图片拼接&#xff0c;而且我希望是可以直接使用代码进行拼接&#xff0c;于是就有了这么一个工具包。 插件效果 通过该插件&#xff0c;我们可以将图片进…

Java开发框架和中间件面试题(5)

44.Tomcat一个请求的处理流程&#xff1f; 假设来自客户的请求为&#xff1a; http&#xff1a;//localhost&#xff1a;8080/test/index.jsp请求被发送到本机端口8080&#xff0c;被在那里侦听Copote HTTP/1.1 Connector,然后 1.Connector把该请求交给它所在的Service的Engi…

STM32MP157D-DK1开发板Qt镜像构建

上篇介绍了STM32MP57-DK1开发板官方系统的烧录。那个系统包含Linux系统的基础功能&#xff0c;如果要进行Qt开发&#xff0c;还需要重新构建带有Qt功能的镜像 本篇就来介绍如何构建带有Qt功能的系统镜像&#xff0c;并在开发板中烧录构建的镜像。 1 Distribution包的构建 ST…