C/C++中volatile的用法

代码编译环境:Windows7 32bits+VS2012。

volatile是“易变的”、“不稳定”的意思。volatile是C/C++的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现的读取错误的问题。


1.volatile的作用

定义为volatile的变量是说这变量可能会被意想不到地改变,编译器在优化时,用到这个变量每次都从重新从RAM读取这个变量的值,而不是使用保存在寄存器里的备份。

在单任务的环境中,一个函数体内部,如果在两次读取变量的值之间的语句没有对变量的值进行修改,那么编译器就会设法对可执行代码进行优化。由于访问寄存器的速度要快过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;
    getchar();
}

程序在VS2012环境下生成Release版本,输出结果是:

10

10

阅读以上程序,注意以下几个要点:

(1)以上代码必须在release模式下考查,因为只有Release模式下才会对程序代码进行优化,而这种优化在变量共享的环境下容易引发问题。

(2)在语句b=i;之前,已经通过内联汇编代码修改了i的值,但是i的变化却没有反映到b中,如果i是一个被多个任务共享的变量,这种优化带来的错误很可能是致命的。

(3)汇编代码[ebp-4]表示变量i的地址,因为ebp是扩展基址指针寄存器,存放函数所属栈的栈底地址。先入栈,占用4个字节,随着函数内申明的局部变量的增多,esp(栈顶指针寄存器)就会相应的减小,因为栈的生长方向由高地址向低地址生长。i为第一个变量,占用4个字节,所以i的地址为[ebp-i]。

那如何抑制编译器对读取变量的这种优化,来防止错误读取呢?

volatile可以轻松胜任,将上面的程序稍作修改,将变量i前申明为volatile即可,观察如下程序:

#include <iostream>
using namespace std;
int main(int argc,char* argv[])
{
    volatile int i=10;
    int a=i;
    cout<<a<<endl;
    _asm{
        mov dword ptr [ebp-4],80
    }
    int b=i;
    cout<<b<<endl;
    getchar();
}

程序输出结果为:

10

80

也就是说,第二次读取变量i的值的时候,已经获得了变化之后的值。跟踪汇编代码可知,凡是申明为volatile的变量,每次都是从内存中读取变量的值,而不是在某些情况下直接从寄存器中取值。


2. volatile变量的应用场合

(1)并行设备的硬件寄存器(如:状态寄存器);

(2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables);

(3)多线程应用中被几个任务共享的变量。

上面这个问题的人是区分C/C++程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。

下面的问题可以看一下面试者伙是不是直正懂得volatile的重要性。

1)一个参数既可以是const还可以是volatile吗?解释为什么。

2) 一个指针可以是volatile 吗?解释为什么。

3) 下面的函数有什么错误:

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

下面是答案:

1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2)是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

3)这段代码有点变态。这段代码的目的是用来返指针*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;
} 

3.嵌入式编程中volatile的作用

嵌入式编程中经常用到 volatile这个关键字,在网上查了下他的用法可以归结为以下两点:

(1)告诉compiler不能做任何优化

比如要往某一地址送两指令:

 int *ip =...; //设备地址
 *ip = 1; //第一个指令
  *ip = 2; //第二个指令 

以上程序compiler可能做优化而成:

int *ip = ...;
*ip = 2; 

结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:

volatile int *ip = ...;
*ip = 1;
*ip = 2; 

即使你要compiler做优化,它也不会把两次赋值语句间化为一,它只能做其它的优化。这对device driver程序员很有用。

(2)表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。如:

volatile char a;
a=0;
while(!a)
{
  //do some things;
}
doother(); 

如果没有 volatile,doother()不会被执行


参考文献:

[1] ]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.

[2]http://www.cnblogs.com/chio/archive/2007/11/24/970632.html

[3]http://www.360doc.com/content/13/0309/22/9290626_270468335.shtml

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 15:50:58

C/C++中volatile的用法的相关文章

c中volatile的用法【转载】

volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化.). 例如: volatile int i=10; int j = i; ... int k = i; volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i

详解C中volatile关键字

volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化.). 例如: volatile int i=10; int j = i; ... int k = i; volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i

java中volatile、synchronized

先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰的变量不允许线

java中volatile关键字的理解

一.基本概念 Java 内存模型中的可见性.原子性和有序性.可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰的变量不允许线程内

Oracle 中 decode 函数用法

Oracle 中 decode 函数用法 含义解释:decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值) 该函数的含义如下:IF 条件=值1 THEN RETURN(翻译值1)ELSIF 条件=值2 THEN RETURN(翻译值2) ......ELSIF 条件=值n THEN RETURN(翻译值n)ELSE RETURN(缺省值)END IFdecode(字段或字段的运算,值1,值2,值3) 这个函数运行的结果是,当字段或字段的运算的值等于值1时,该函数返回值

linux中快捷键的用法

linux中快捷键的用法 1.1 常见快捷键 ctrl + a 把光标移动到行首 ctrl + e 把光标移动到行尾 ctrl + c 撤销当前的操作=cancel ctrl + d logout命令(当前行没有任何内容,退出当前用户),删除光标所在位置后面的一个符号,文本 ctrl + l(小写字母L)清除屏幕内容 ctrl +u 剪切光标所在位置到行首内容 ctrl +k 剪切光标所在位置到行尾内容 ctrl +y 粘贴 ctrl + r search搜索历史命令,继续搜索,查找历史命令 

thinkphp中I(&quot;parm&quot;)用法的注意事项

php中用来传输的方式有两种$_GET 和 $_POST, thinkphp 提供了一种兼容两中传输方式的做法I(“param”) 但是请注意:在使用php中的函数时需要传入参数时不能用I(“parm”),必须使用采用的传输方式$_GET 或 $_POST传入,否则出错 thinkphp中I("parm")用法的注意事项,布布扣,bubuko.com

ASP.NET在实际开发中验证码的用法

在网上有看到很多关于验证码的代码,很多都只是生成一张验证码图片,然而在实际登陆验证模块,验证码要怎么添加进去或者说怎么运用.和实际项目开发中要怎么使用验证码,我自己总结了几点. 一.在实际开发登陆模块的验证码,程序员是将验证码的文本值(字符串)存在Session中的,然后在登陆验证的时候,通过Session取值进行判断的,这样效率会高很多. 二.然而在写验证码的时候要想通过Session存值,就必须实现System.Web.SessionState.IRequiresSessionState这个

objective-c 中随机数的用法 (3种:arc4random() 、random()、CCRANDOM_0_1() )

1.随机数的使用      1).arc4random() 比较精确不需要生成随即种子 使用方法 : 通过arc4random() 获取0到x-1之间的整数的代码如下: int value = arc4random() % x; 获取1到x之间的整数的代码如下: int value = (arc4random() % x) + 1; 2).CCRANDOM_0_1() cocos2d中使用 ,范围是[0,1] 使用方法: float random = CCRANDOM_0_1() * 5; //