标签:
volatile 是“易变的”、“不稳定”的意思。volatile是 c++ 的一个关键字,用来解决在“共享”环境下容易出现的读取错误的问题。
在单任务的环境中,一个函数体内部,如果在两次读取变量的值之间的语句没有对变量的值进行修改,那么编译器就会设法对可执行代码进行优化。由于访问寄存器的速度要快过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;
return 0;
}
程序在VS2012环境下生成 release 版本(一定要极端优化,vs编译环境下选择优化 速度最大化 /O2),输出结果也是:
10
10
顺便说一下,ebp是扩展基址指针寄存器(extended base pointer) 其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部。
本来事实上已经通过内联汇编,修改过的值,为什么打印出来还是10呢
但是如果:
将 int i=10; 前加 volatile 就不会发生这种情况了。
跟踪汇编代码可以发现,凡是声明为 volatile 的变量,每次拿到的值都是从内存中直接读取的。
以下实验在 vs2012 release 环境下进行。
int i=10;
int a=i;
tmp(a);
00D71273 push dword ptr ds:[0D73024h]
00D71279 mov ecx,dword ptr ds:[0D7303Ch]
00D7127F push 0Ah
00D71281 call dword ptr ds:[0D7302Ch]
00D71287 mov ecx,eax
00D71289 call dword ptr ds:[0D73028h]
_asm{
mov dword ptr [ebp-4],80
00D7128F mov dword ptr [ebp-4],50h
}
int b=i;
tmp(b);
00D71296 push dword ptr ds:[0D73024h]
00D7129C mov ecx,dword ptr ds:[0D7303Ch]
00D712A2 push 0Ah
00D712A4 call dword ptr ds:[0D7302Ch]
00D712AA mov ecx,eax
00D712AC call dword ptr ds:[0D73028h]
tmp(a);
01201274 push dword ptr ds:[1203024h]
volatile int i=10;
0120127A mov dword ptr [i],0Ah
int a=i;
01201281 mov eax,dword ptr [i]
tmp(a);
01201284 mov ecx,dword ptr ds:[120303Ch]
0120128A push eax
0120128B call dword ptr ds:[120302Ch]
01201291 mov ecx,eax
01201293 call dword ptr ds:[1203028h]
_asm{
mov dword ptr [ebp-4],80
01201299 mov dword ptr [i],50h
}
int b=i;
012012A0 mov eax,dword ptr [i]
tmp(b);
012012A3 push dword ptr ds:[1203024h]
012012A9 mov ecx,dword ptr ds:[120303Ch]
012012AF push eax
tmp(b);
012012B0 call dword ptr ds:[120302Ch]
012012B6 mov ecx,eax
012012B8 call dword ptr ds:[1203028h]
由于编译器的极端优化,可以很明显的看到,在没有加 volatile 的情况下,甚至编译器是直接使用操作数 0Ah 进行运算的。
而在加了 volatile 的情况下,每次都是从 ptr [i] 中读取。
而且在速度极端优化的情况下,
void tmp(int t) {
cout<<t<<endl;
}
也自动 inline 处理了。
但是这里也抛出一个问题,为什么是 [ebp-4] 修改的就是i的值,更奇怪的是,我如果如下这样写代码,那改的会是哪个变量的值呢:
#include <iostream>
using namespace std;
void tmp(int t) {
cout<<t<<endl;
}
int main(int argc,char* argv[])
{
volatile int ic=12;
volatile int i=10;
int a=i;
volatile int ib=11;
tmp(a);
tmp(ib);
tmp(ic); //必须使用,如果不使用,编译器优化为使用同一块内存地址
_asm{
mov dword ptr [ebp-4],80
}
int b=i;
tmp(b);
return 0;
}
为什么分配的总是 [ebp-4] 是复制给 a 的值呢?试验过,如果将 ic 赋值给 a,那 [ebp-4] 存放的值将会是 ic
阅读以上程序,注意以下几个要点:
(1)以上代码必须在release模式下考查,因为只有Release模式(严格说需要在速度最大优化 /O2)下才会对程序代码进行优化,而这种优化在变量共享的环境下容易引发问题。
(2)凡是需要被多个任务共享的变量(如可能被中断服务程序访问的变量、被其他线程访问的变量等),都应声明为 volatile 变量。而且为了提高执行效率,要减少对 volatile 不必要的使用。
(3)由于优化可能会将一些“无用”的代码彻底去除,所以,如果确实希望在可执行文件中保留这部分代码,也可以将其中的变量声明为 volatile:
int main(int argc,char* argv[])
{
int s,i,j;
for(i=0;i<100;++i)
for(j=0;j<100;++j)
s=5;
return 0;
}
在生成 release 版本的程序时,由于循环体每次给 s 的值不变(简化为执行1次),或者说没有使用(1次都没有),但如果此时程序猿是希望循环拖延时间,写成 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;
}
标签:
原文地址:http://blog.csdn.net/scythe666/article/details/51335948