您的位置:首页 > 编程语言 > C语言/C++

c++关键字之:volatile

2017-03-30 19:19 344 查看
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 环境下进行。
不加 volatile
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]


加了 volatile  
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>
usingnamespacestd;

void tmp(int t) { cout<<t<<endl; }

int main(int argc,char* argv[])
{
volatileint ic=12;
volatileint i=10;
int a=i;
volatileint ib=11;
tmp(a);
tmp(ib);
tmp(ic); //必须使用,如果不使用,编译器优化为使用同一块内存地址

_asm{
mov dwordptr [ebp-4],80
}

int b=i;
tmp(b);

return0;
}


为什么分配的总是 [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;

return0;
}


在生成 release 版本的程序时,由于循环体每次给 s 的值不变(简化为执行1次),或者说没有使用(1次都没有),但如果此时程序猿是希望循环拖延时间,写成 volatile 就可以了。
附录:问题
1)一个参数既可以是const还可以是volatile吗?解释为什么
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2) 一个指针可以是volatile 吗?解释为什么
是的。尽管这并不很常见。一个例子是当一个中断服务子程序修该一个指向一个buffer的指针时。
3) 下面的函数有什么错误:

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


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

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


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

long square(volatileint *ptr) {
int a;
a = *ptr;
returna * a;
}


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息