码迷,mamicode.com
首页 > 编程语言 > 详细

c++关键字之:volatile

时间:2016-05-07 07:00:53      阅读:283      评论:0      收藏:0      [点我收藏+]

标签:

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>
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; 
} 

c++关键字之:volatile

标签:

原文地址:http://blog.csdn.net/scythe666/article/details/51335948

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!