C/C++中volatile关键字详解

 1. 为什么用volatile?

    C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier。这是 BS 在 "The C++ Programming Language" 对 volatile 修饰词的说明:

A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

      volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:

	volatile int i=10;int a = i;...// 其他代码,并未明确告诉编译器,对 i 进行过操作int b = i;

 

    volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。注意,在 VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无 volatile 关键字,对程序最终代码的影响:

输入下面的代码:

	#include <stdio.h>void main(){int i = 10;int a = i;printf("i = %d", a);// 下面汇编语句的作用就是改变内存中 i 的值// 但是又不让编译器知道__asm {mov dword ptr [ebp-4], 20h}int b = i;printf("i = %d", b);}

 

    然后,在 Debug 版本模式运行程序,输出结果如下:

i = 10
i = 32

    然后,在 Release 版本模式运行程序,输出结果如下:

i = 10
i = 10

    输出的结果明显表明,Release 模式下,编译器对代码进行了优化,第二次没有输出正确的 i 值。下面,我们把 i 的声明加上 volatile 关键字,看看有什么变化:

	#include <stdio.h>void main(){volatile int i = 10;int a = i;printf("i = %d", a);__asm {mov dword ptr [ebp-4], 20h}int b = i;printf("i = %d", b);}

 

    分别在 Debug 和 Release 版本运行程序,输出都是:

i = 10
i = 32

    这说明这个 volatile 关键字发挥了它的作用。其实不只是“内嵌汇编操纵栈”这种方式属于编译无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile用在如下的几个地方:
1) 中断服务程序中修改的供其它程序检测的变量需要加volatile;
2) 多任务环境下各任务间共享的标志应该加volatile;
3) 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

2.volatile 指针

    和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:

  • 修饰由指针指向的对象、数据是 const 或 volatile 的:

    	const char* cpch;volatile char* vpch;
     

    注意:对于 VC,这个特性实现在 VC 8 之后才是安全的。

  • 指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:

    	char* const pchc;char* volatile pchv;

    注意:(1) 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象

          (2) 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
              (3) C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

3. 多线程下的volatile   

    有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下: 

  volatile  BOOL  bStop  =  FALSE;  
   (1) 在一个线程中:  
  while(  !bStop  )  {  ...  }  
  bStop  =  FALSE;  
  return;    
   (2) 在另外一个线程中,要终止上面的线程循环:  
  bStop  =  TRUE;  
  while(  bStop  );  //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。
    这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:  
  ...  
  int  nMyCounter  =  0;  
  for(;  nMyCounter<100;nMyCounter++)  
  {  
  ...  
  }  
  ...  
   在此段代码中,nMyCounter的拷贝可能存放到某个寄存器中(循环中,对nMyCounter的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter  -=  1;这个操作中,对nMyCounter的改变是对内存中的nMyCounter进行操作,于是出现了这样一个现象:nMyCounter的改变不同步。

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

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

相关文章

【Git】 git push origin master Everything up-to-date报错

hello&#xff0c;我是索奇&#xff0c;可以叫我小奇 git push 出错&#xff1f;显示 Everything up-to-date 那么看看你是否提交了message 下面是提交的简单流程 git add . git commit -m "message" git push origin master 大多数伙伴是没写git commit -m "…

AI自动驾驶

AI自动驾驶 一、自动驾驶的原理二、自动驾驶的分类三、自动驾驶的挑战四、自动驾驶的前景五、关键技术六、自动驾驶的安全问题七、AI数据与自动驾驶八、自动驾驶的AI算法总结 自动驾驶技术是近年来备受关注的热门话题。它代表了人工智能和机器学习在汽车行业的重要应用。本文将…

UML之四种事物

目录 结构事物 行为事物 分组事物&#xff1a; 注释事物 结构事物 1.类(Class) -类是对一组具有相同属性、方法、关系和语义的对象的描述。一个类实现一个或多个接口 2.接口(interface) -接口描述 了一个类或构件的一个服务的操作集。接口仅仅是定义了一组操作的规范&…

案例16 基于Spring Boot实现学生新增案例

基于Spring Boot实现学生新增。 1. 创建Spring Boot项目 创建Spring Boot项目&#xff0c;项目名称为case16-springboot-student01。 ​ 2. 设置项目信息 ​ 3. 选择依赖 选择Lombok ​ 选择Spring Web ​ 4. 设置项目名称 ​ 5. Maven依赖 <?xml version"1.0&qu…

Nature子刊 |肠道宏病毒组揭示百岁老人长寿秘诀

发表期刊&#xff1a;nature microbiology 发表时间&#xff1a;2023 影响因子&#xff1a;28.3 DOI: 10.1038/s41564-023-01370-6 研究背景 衰老是一种不可逆转的自然过程&#xff0c;随着年龄的增长&#xff0c;机体诸多方面出现功能性下降&#xff0c;与衰老相关的疾病&a…

生成式AI颠覆传统数据库的十种方式

对于生成式AI的所有闪光点&#xff0c;这个新时代最大的转变可能深埋在软件堆栈中。AI算法正在不易觉察地改变一个又一个数据库。他们正在用复杂、自适应且看似更直观的AI新功能颠覆传统数据库。 目录 1、向量和嵌入 2、查询模型 3、建议 4、索引范例 5、数据分类 6、更…

Unity 框架学习--1

由浅入深&#xff0c;慢慢演化实现框架 两个类的实现代码完全一样&#xff0c;就只有类名或类型不一样的时候&#xff0c;而且还需要不断扩展&#xff08;未来会增加各种事件&#xff09;的时候&#xff0c;这时候就用 泛型 继承 来提取&#xff0c;继承解决扩展的问题&#…

【RabbitMQ与SpringBoot集成测试收发消息】

【RabbitMQ与SpringBoot集成测试收发消息】 一、环境说明二、实验步骤三、小结 一、环境说明 安装环境&#xff1a;虚拟机VMWare Centos7.6 Maven3.6.3 JDK1.8RabbitMQ版本&#xff1a;rabbitmq-server-3.8.8-1.el7.noarch.rpm编程工具Idea 运行JDK为17 二、实验步骤 在Rab…

List和数组互转方法以及踩坑点

一、数组转List 1. 使用for循环逐个添加 String[] arr {"A", "B", "C"}; List<String> list new ArrayList<>(); for (String element : arr) {list.add(element); }2. 使用Arrays.asList(arr) String[] arr {"A", …

TypeScript 泛型的深入解析与基本使用

系列文章目录 文章目录 系列文章目录前言一、泛型的概念二、泛型函数三、泛型类四、泛型接口五、泛型约束总结前言 泛型是TypeScript中的一个重要概念,它允许我们在定义函数、类或接口时使用参数化类型,增强了代码的灵活性和重用性。本文将深入探讨泛型的概念,以及如何在Ty…

智能驾驶系列报告之一:智能驾驶 ChatGPT时刻有望来临

原创 | 文 BFT机器人 L3 功能加速落地&#xff0c;政策标准有望明确 L2 发展日益成熟&#xff0c;L3 功能加速落地。根据市场监管总局发布的《汽车驾驶自动化分级》与 SAE发布的自动驾驶分级标准&#xff0c;自动驾驶主要分为 6 个级别&#xff08;0 级到 5 级&#xff0c;L0 …

Tomcat多实例部署及nginx+tomcat的负载均衡和动静分离

Tomcat多实例部署 安装 jdk、tomcat&#xff08;流程可看之前博客&#xff09; 配置 tomcat 环境变量 [rootlocalhost ~]# vim /etc/profile.d/tomcat.sh#tomcat1 export CATALINA_HOME1/usr/local/tomcat/tomcat1 export CATALINA_BASE1/usr/local/tomcat/tomcat1 export T…

Delphi调用WindowsAPI获取窗口进程

Delphi有封装的很好的WindowsAPI&#xff0c;直接调用即可&#xff0c;大体上和C差不多&#xff0c;有些地方需要额外处理。 给出一个实例&#xff1a; varg_process: THandle;procedure initGlobal(); beginvarg_handle: HWND;g_handle : FindWindow(clsName, name);if g_ha…

矩阵定理复习记录

矩阵复习 矩阵导数定理 若A是一个如下矩阵&#xff1a; A [ a 11 a 12 a 21 a 22 ] A \begin{bmatrix}a_{11}&a_{12}\\a_{21}&a_{22}\end{bmatrix} A[a11​a21​​a12​a22​​] y是一个向量矩阵&#xff1a; y ⃗ [ y 1 y 2 ] \vec{y}\begin{bmatrix}y_1\\y_2\e…

在vue项目使用数据可视化 echarts ,柱状图、折线图、饼状图使用示例详解及属性详解

官网地址&#xff1a;Apache ECharts ​一、下载插件并在页面中引入 npm install echarts --save 页面导入&#xff1a; import * as echarts from echarts 全局导入&#xff1a; main.js 中&#xff0c;导入并注册到全局 import echarts from echarts Vue.prototype.$echart…

Clone函数

概述 Clone函数是一种用于复制的计算机函数。在程序编写中&#xff0c;除了自定义一个拷贝构造函数来实现对象复制外&#xff0c;还可以实现一个clone函数。这需要借助编译器实现的一个隐藏拷贝构造函数&#xff0c;这样的做法&#xff0c;更省心。 中文名clone函数外文名clon…

C# 使用FFmpeg.Autogen对byte[]进行编解码

C# 使用FFmpeg.Autogen对byte[]进行编解码&#xff0c;参考&#xff1a;https://github.com/vanjoge/CSharpVideoDemo 入口调用类&#xff1a; using System; using System.IO; using System.Drawing; using System.Runtime.InteropServices; using FFmpeg.AutoGen;namespace F…

C++11异步与通信之 packaged_task

概念简介 packaged_task 用于包装可调用目标(Callable)为一个对象,如lambda&#xff0c;普通函数&#xff0c;小括号重载等&#xff0c;用于异步调用。 其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中&#xff0c;和promise类似。 将函数的调用与函数返…

时序预测 | MATLAB实现EEMD-LSTM、LSTM集合经验模态分解结合长短期记忆神经网络时间序列预测对比

时序预测 | MATLAB实现EEMD-LSTM、LSTM集合经验模态分解结合长短期记忆神经网络时间序列预测对比 目录 时序预测 | MATLAB实现EEMD-LSTM、LSTM集合经验模态分解结合长短期记忆神经网络时间序列预测对比效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 时序预测 | …

小龟带你妙写排序之快速排序

快速排序 一. 快速排序原理二. 题目三. 快速排序的思路分析&#xff08;图文结合&#xff09;四.代码 一. 快速排序原理 先从数据序列中选一个元素&#xff0c;并将序列中所有比该元素小的元素都放到它的右边或左边&#xff0c;再对左右两边分别用同样的方法处之直到每一个待处…