volatile总结

来源:互联网 发布:中国如何注册io域名 编辑:程序博客网 时间:2024/05/01 20:17
1)基本用法       

        volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

        volatile的本意是“易变的”,不过翻译成“直接存取原始内存地址”更为合适。“易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化。

        使用该关键字的例子如下: 

int volatile nVint;//当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
        对于这样的代码:
volatile int i = 10;int a = i;... //其他代码,并未明确告诉编译器,对i进行过操作int b = i;
        volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。

        一般说来,volatile用在如下的几个地方:

  1. 像中断处理程序之类的异步进程中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义。

        另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
      除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
      注意:可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
      一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

2) volatile 指针

       和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:

  • 修饰由指针指向的对象、数据是 const 或 volatile 的:

    const char* cpch; volatile char* vpch;

    注意:对于 VC,这个特性实现在 VC 8 之后才是安全的。

  • 指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:

    char* const pchc;  char* volatile pchv;  

注意:

(1) 可以把一个非 volatile int 赋给 volatile int,但是不能把非 volatile 对象赋给一个 volatile 对象

(2) 除了基本类型外,对用户定义类型也可以用 volatile 类型进行修饰。

(3) C++ 中一个有 volatile 标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用 const_cast 来获得对类型接口的完全访问。此外,  volatile 向 const 一样会从类传递到它的成员。

3) 多线程下的 volatile

        有些变量是用 volatile 关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入  CPU 寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile 的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:

volatile BOOL bStop = FALSE;

(1) 在一个线程中: 
while( !bStop ) { ... }  bStop = FALSE; return;   
(2) 在另外一个线程中,要终止上面的线程循环: 
bStop = TRUE;  while( bStop ); //等待上面的线程终止  
        如果 bStop 不使用 volatile 申明,那么这个循环将是一个死循环,因为 bStop 已经读取到了寄存器中,寄存器中 bStop 的值永远不会变成 FALSE,加上 volatile,程序在执行时,每次均从内存中读出 bStop 的值,就不会死循环了。

        这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中: 

...  int nMyCounter = 0;  for(; nMyCounter<100;nMyCounter++)  {      ...   }...   
        在此段代码中,nMyCounter 的拷贝可能存放到某个寄存器中(循环中,对 nMyCounter 的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1;这个操作中,对 nMyCounter 的改变是对内存中的 nMyCounter 进行操作,于是出现了这样一个现象:nMyCounter 的改变不同步。

深入

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



       
原创粉丝点击