volatile
字面意思:易失的;易变的;易挥发的
volatile 关键字是一种类型修饰符,和const对应,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,系统总是重新从它所在的内存读取数据,从而可以提供对特殊地址的稳定访问。
易变性:
例如:
volatile int i=10;
int a = i;
...// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;
注: volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
注意,在debug环境下编译器未对代码进行优化,所以看不出volatile的作用;在release环境下编译器对代码进行了优化,可以看出volatile的作用。
例子:
#include <stdio.h>
#include<iostream>
using namespace std;
int main()
{
volatile int i = 10;
int a = i;
printf("i = %d", a);
// 下面汇编语句的作用就是改变内存中 i 的值
// 但是又不让编译器知道
__asm {
mov dword ptr[ebp - 4], 20h
//将20h(也就是32)赋值给ebp向下4的偏移处位置的变量,ebp指针指向a,ebp-4指向i
//dword ptr意思是把偏移处解释为双字指针,即指向4个字节的内存空间的指针。因为是int为4个字节
}
int b = i;
printf("i = %d", b);
system("pause");
return 0;
}
在release环境下编译:
此时由于volatile关键字的作用,可以获取到正确的i值
若没有volatile关键字,则是:
将会读取到错误的i值
不可优化性:体现再volatile变量之间
若没有volatile关键字,编译器可以根据实际情况,对变量的代码进行优化。但是有volatile关键字,使得每次读取变量都需要访问内存,将不再对变量的代码进行优化。
顺序性:
两个非volatile变量:
全局变量A,B均为非volatile变量。通过gcc O2优化进行编译,你可以惊奇的发现,A,B两个变量的赋值顺序被调换了!!!在对应的汇编代码中,B = 0语句先被执行,然后才是A = B + 1语句被执行。
在这里,我先简单的介绍一下C/C++编译器最基本优化原理:保证一段程序的输出,在优化前后无变化。将此原理应用到上面,可以发现,虽然gcc优化了A,B变量的赋值顺序,但是foo()函数的执行结果,优化前后没有发生任何变化,仍旧是A = 1;B = 0。因此这么做是可行的。
一个volatile变量,一个非volatile变量:
B仍旧被提前到A之前赋值,Volatile变量B,并未阻止编译器优化的发生,编译后仍旧发生了乱序现象。
如此看来,C/C++ Volatile变量,与非Volatile变量之间的操作,是可能被编译器交换顺序的。
两个volatile变量:
同时将A,B两个变量都声明为volatile变量,再来看看对应的汇编。奇迹发生了,A,B赋值乱序的现象消失。此时的汇编代码,与用户代码顺序高度一直,先赋值变量A,然后赋值变量B。
如此看来,C/C++ Volatile变量间的操作,是不会被编译器交换顺序的。
小结:C/C++ Volatile关键词的第三个特性:”顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。Volatile变量与非Volatile变量的顺序,编译器不保证顺序,可能会进行乱序优化。同时,C/C++ Volatile关键词,并不能用于构建happens-before语义,因此在进行多线程程序设计时,要小心使用volatile,不要掉入volatile变量的使用陷阱之中。
使用volatile的三个情况:
- 中断服务程序中修改的供其它程序检测的变量需要加volatile;
- 多任务环境下各任务间共享的标志应该加volatile;
- 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;