c++关键字之:volatile

来源:互联网 发布:广东干部网络课程技巧 编辑:程序博客网 时间:2024/05/29 07:43

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],8000D7128F  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],8001201299  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; } 
1 0
原创粉丝点击