知识库之volatile关键字

来源:互联网 发布:mac精确模式怎么退出 编辑:程序博客网 时间:2024/05/15 02:07

一. volatile的定义

        一个定义为volatile的变量是说这变量可能会被意想不到的改变,这样编译器就不会去假设这个变量的值了。准确的说是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器的备份。

        也许,大家还不是很理解,volatile到底有什么用,别急,且听慢慢道来。

        1)由于访问寄存器的速度要快于RAM,因此,编译器一般会做减少存取外部RAM的优化;

        2)编译器使用一种技术名叫数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并、常量传播等优化。

        这样,当变量在定义和使用之间的代码没有改变此变量值时,其值可能保存到某个寄存器中,当使用时,直接从寄存器中取值,而不去内存中取值,这就是编译器优化代码的结果。


二. volatile的作用

     volatile是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等,volatile对应的变量可能在你的程序本身不知道的情况下发生改变,比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量,你自己的程序,是无法判定何时这个变量会发生变化;还比如,此变量和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

     volatile字面解释为“易变的”,但其实不准确,其易变是因为外在因素引起的,如多线程、中断等(即不被程序指明的方式改变),被voliate修饰的变量在存取时都会重新读取内存的值,所以volatile应该理解为“直接存取原始内存地址”。


三. volatile的适用范围

     一般在嵌入式程序开发中常用。其适用的实例如下:

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

       2)一个中断服务子程序中会访问到的非自动变量,或者说中断服务子程序中修改的供其他程序检测或适用的变量

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

       上面1)说的情形暂未遇到过,暂且不谈;

       上面2)说的情形,如下,程序本意是希望ISR_2中断产生时,在main中调用dosomething()函数,但由于编译器判断在main中没有修改过i,因此可能只执行一次对i从内存读取到某个寄存器中,然后每次if判断都只使用这个寄存器里面的“副本”,导致dosomething()永远不会执行,但在i前加上volatile修饰,则编译器保证对此变量的读写都会对其对应的内存进行:         

static int i = 0;int main(void){     ...     while(1)     {         if(i) dosomething();      }}/*interrupt service routine*/void ISR_2(void){    i = 1;}
           上面说的3)解释如下:
  在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后再取变量值时,就直接从寄存器中取值;变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致;变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致;当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致
四. 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; 
        } 

     对1)2)问题的另一种解释:

     volatile修饰符告诉complier变量值可以以任何不被程序明确指明的方式改变,最常见的例子就是外部端口的值,它的变化可以不用程序内的任何赋值语句就有可能改变的,这种变量就可以用volatile来修饰,complier不会优化掉它。const修饰的变量在程序里面是不能改变的,但是可以被程序外的东西修改,就象上面说的外部端口的值,如果仅仅使用const,有可能complier会优化掉这些变量,加上volatile就万无一失了。所以使用const volatile修饰变量表明变量值只由外部条件改变,不会出现其它的副作用。
     例如const volatile char *port  (const volatile char *)0x00ff;




原创粉丝点击