代码编译环境:Windows7 32bits+VS2012。

volatile是“易变的”、“不稳定”的意思。volatile是C的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现读取错误的问题。

1.volatile的作用

定义为volatile的变量是说这变量可能会被意想不到地改变,即在你程序运行过程中一直会变,你希望这个值被正确的处理,每次从内存中去读这个值,而不是因编译器优化从缓存的地方读取,比如读取缓存在寄存器中的数值,从而保证volatile变量被正确的读取。

在单任务的环境中,一个函数体内部,如果在两次读取变量的值之间的语句没有对变量的值进行修改,那么编译器就会设法对可执行代码进行优化。由于访问寄存器的速度要快过RAM(从RAM中读取变量的值到寄存器),以后只要变量的值没有改变,就一直从寄存器中读取变量的值,而不对RAM进行访问。

而在多任务环境中,虽然在一个函数体内部,在两次读取变量之间没有对变量的值进行修改,但是该变量仍然有可能被其他的程序(如中断程序、另外的线程等)所修改。如果这时还是从寄存器而不是从RAM中读取,就会出现被修改了的变量值不能得到及时反应的问题。如下程序对这一现象进行了模拟。

#include <iostream>
using namespace std;int main(int argc,char* argv[])
{int i=10;int a=i;cout<<a<<endl;_asm{mov dword ptr [ebp-4],80}int b=i;cout<<b<<endl;
}

序在VS2012环境下生成Release版本,输出结果是:

10
10

阅读以上程序,注意以下几个要点: 
(1)以上代码必须在Release模式下考查,因为只有Release模式下才会对程序代码进行优化,而这种优化在变量共享的环境下容易引发问题。

(2)在语句b=i;之前,已经通过内联汇编代码修改了i的值,但是i的变化却没有反映到b中,如果i是一个被多个任务共享的变量,这种优化带来的错误很可能是致命的。

(3)汇编代码[ebp-4]表示变量i的存储单元,因为ebp是扩展基址指针寄存器,存放函数所属栈的栈底地址,先入栈,占用4个字节。随着函数内申明的局部变量的增多,esp(栈顶指针寄存器)就会相应的减小,因为栈的生长方向由高地址向低地址生长。i为第一个变量,栈空间已被ebp入栈占用了4个字节,所以i的地址为ebp-i,[ebp-i]则表示变量i的存储单元。

那如何抑制编译器对读取变量的这种优化,来防止错误读取呢?volatile可以轻松胜任,将上面的程序稍作修改,将变量i前申明为volatile即可,观察如下程序:

#include <iostream>
using namespace std;int main(int argc,char* argv[])
{volatile int i=10;int a=i;cout<<a<<endl;_asm{mov dword ptr [ebp-4],80}int b=i;cout<<b<<endl;getchar();
}

程序输出结果为: 
10 
80 

也就是说,第二次读取变量i的值的时候,已经获得了变化之后的值。跟踪汇编代码可知,凡是申明为volatile的变量,每次都是从内存中读取变量的值,而不是在某些情况下直接从寄存器中取值。

2.volatile应用场景

(1)并行设备的硬件寄存器(如状态寄存器)。 
假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。

int  *output = (unsigned  int *)0xff800000; //定义一个IO端口;
int   init(void)
{  int i;  for(i=0;i< 10;i++){  *output = i;  }
}

经过编译器优化后,编译器认为前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为 9,所以编译器最后给你编译编译的代码结果相当于:

int  init(void)
{  *output = 9;
}

如果你对此外部设备进行初始化的过程是必须是像上面代码一样顺序的对其赋值,显然优化过程并不能达到目的。反之如果你不是对此端口反复写操作,而是反复读操作,其结果是一样的,编译器在优化后,也许你的代码对此地址的读操作只做了一次。然而从代码角度看是没有任何问题的。这时候就该使用volatile通知编译器这个变量是一个不稳定的,在遇到此变量时候不要优化。

(2)一个中断服务子程序中访问到的变量;

static int i=0;int main()
{while(1){if(i) dosomething();}
}/* Interrupt service routine */
void IRS()
{i=1;
}

上面示例程序的本意是产生中断时,由中断服务子程序IRS响应中断,变更程序变量i,使在main函数中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远不会被调用。如果将变量i加上volatile修饰,则编译器保证对变量i的读写操作都不会被优化,从而保证了变量i被外部程序更改后能及时在原程序中得到感知。

(3)多线程应用中被多个任务共享的变量。 
当多个线程共享某一个变量时,该变量的值会被某一个线程更改,应该用 volatile 声明。作用是防止编译器优化把变量从内存装入CPU寄存器中,当一个线程更改变量后,未及时同步到其它线程中导致程序出错。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。示例如下:

volatile  bool bStop=false;  //bStop 为共享全局变量
//第一个线程
void threadFunc1()
{...while(!bStop){...}
}//第二个线程终止上面的线程循环
void threadFunc2()
{...bStop = true;
}

要想通过第二个线程终止第一个线程循环,如果bStop不使用volatile定义,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成true,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。

是否了解volatile的应用场景是区分C/C++程序员和嵌入式开发程序员的有效办法,搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,这些都要求用到volatile变量,不懂得volatile将会带来程序设计的灾难。

3.volatile常见问题

下面的问题可以看一下面试者是不是直正了解volatile。 
(1)一个参数既可以是const还可以是volatile吗?为什么? 
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

(2)一个指针可以是volatile吗?为什么? 
是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

(3)下面的函数有什么错误?

int square(volatile int *ptr)
{ return *ptr * *ptr;
} 

这段代码有点变态,其目的是用来返回指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)
{ int a,b; a = *ptr; b = *ptr; return a * b;
} 

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{ int a; a = *ptr; return a * a;
} 

4.嵌入式编程中volatile的作用

嵌入式编程中经常用到 volatile这个关键字,常见用法可以归结为以下两点:

(1)告诉compiler不能做任何优化。比如要往某一地址送两指令。

 int *ip =...;  //设备地址 *ip = 1;       //第一个指令 *ip = 2;       //第二个指

以上程序compiler可能做优化而成:

int *ip = ...;
*ip = 2; 

结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:

volatile int *ip = ...;
*ip = 1;
*ip = 2; 

(2)volatile定义的变量如果在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。如:

volatile char a;
a=0;
while(!a)
{ //do some things;
}
doother(); 

如果没有 volatile,doother()不会被执行。

volatile能够避免编译器优化带来的错误,但使用volatile的同时,也需要注意频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。


参考文献:

[1] ]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008. 
[2]C中的volatile用法 
[3]ebp与esp讲解 
[4]C语言再学习 – 关键字volatile 
[5]C语言中volatile关键字的作用

C++中volatile的作用相关推荐

  1. vc中 volatile 的作用

    vc中 volatile 的作用 volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如 操作系统.硬件或者其它线程等.遇到这个关键字声明的变量,编译器对访 ...

  2. Java中volatile的作用以及用法

    volatile让变量每次在使用的时候,都从主存中取.而不是从各个线程的"工作内存". volatile具有synchronized关键字的"可见性",但是没有 ...

  3. 单例模式双重锁中volatile的作用

    对于学android开发的同学来说,单例模式应该在熟悉不过了吧,单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,还可以分为饿汉式和懒汉式,这篇文章浅谈下懒汉式,重点 ...

  4. 【软件开发底层知识修炼】二十八 C/C++中volatile的作用

    上一篇文章学习了C/C++中的指针与数组的区别,点击链接进行查看:[软件开发底层知识修炼]二十七 C/C++中的指针与数组是不同的 本篇文章将学习volatile关键字在C/C++中的作用 文章目录 ...

  5. Java双重检查懒汉式单例模式中volatile的作用

    先看下懒汉式单例模式双重检查的写法 public class Lazy2 {private volatile static Lazy2 instance;private Lazy2 (){}publi ...

  6. java中关键字volatile的作用(转载)

    转载:http://blog.csdn.net/orzorz/article/details/4319055 用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对 ...

  7. [转载] 浅析Java中volatile关键字及其作用

    参考链接: Java中的volatile关键字 在 Java 多线程中如何保证线程的安全性?那我们可以使用 Synchronized 同步锁来给需要多个线程访问的代码块加锁以保证线程安全性.使用 sy ...

  8. java 关键字volatile的作用

    用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况.volatile就是用来 ...

  9. 讲讲volatile的作用

    讲讲volatile的作用 讲讲volatile的作用 254 推荐 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了.精确地说就是,优化器在用 ...

  10. 每日一题(17)—— 关键字volatile的作用和三个不同的应用场合

    关键字volatile的作用和三个不同的应用场合 作用:防止编译器优化: (1)并行设备的硬件寄存器(如状态寄存器):// #define S5PV210_GPIO_DATA (*(volatile ...

最新文章

  1. 干货 | 上手机器学习,从搞懂这十大经典算法开始
  2. 【收集】Web开发工具
  3. html5 webDatabase 存储中sql语句执行可嵌套使用
  4. Eclipse——UML类图插件
  5. linux系统fuser命令,Linux系统使用Fuser命令的方法
  6. 到底什么培训适合你?
  7. 结构体定义小的放前面_编程C语言进阶篇——自定义数据类型:结构体
  8. 股票卖出以后可以立即把钱转出吗?
  9. pdns backend mysql_安装PowerDNS(与MySQL后端)和Poweradmin在Debian蚀刻
  10. 15.企业应用架构模式 --- 分布模式
  11. 计算机网络体系结构作业题整理-第一章答案
  12. hustoj Runtime Error (运行错误)的解决方法
  13. 那些想替代 C 的语言怎么样?Go、Rust、C++ 和 Zig 生产力对比
  14. Steamwoks上传游戏及提交审核指南
  15. 机器学习基础补习04---凸优化
  16. windows的hosts文件位置
  17. 【Unity面试】 2022年Unity面试题分享 | 全面总结 | 建议收藏
  18. 视频融合应用没听说过?
  19. 基于单片机的超市收银机
  20. javaScript 生成随机字母 随机数字的5种方法

热门文章

  1. python接口测试_【Python自学】Python接口自动化测试的学习 - 伊凡Ivan
  2. mysql var目录很快_mysql的这些坑你踩过吗?快来看看怎么优化mysql
  3. python是免费的、开源的、跨平台的_NovalIDE是一款开源,跨平台,而且免费的国产Python IDE。...
  4. vant部署_Vue 3.x配置Vuex使用Vant TabBar及部署
  5. 5分钟搞懂如何在Spring Boot中Schedule Tasks
  6. Monitor HDU6514 二维差分入门学习
  7. export和import 输出/接收模块变量的接口
  8. Dapper学习 - Dapper.Rainbow(三) - Read
  9. 用c#开发微信(10) JSSDK 基本用法 分享接口“发送到朋友”
  10. [转载] 周爱民:《大道至简》创作的幕后故事