zoukankan      html  css  js  c++  java
  • (转)volatile的使用

    很多人对Volatile都不太了解,其实Volatile是由于编译器优化所造成的一个Bug而引入的关键字。
             int a = 10;
             int b = a;
             int c = a;
             理论上来讲每次使用a的时候都应该从a的地址来读取变量值,但是这存在一个效率问题,就是每次使用a都要去内存中取变量值,然后再通过系统总线传到CPU处理,这样开销会很大。所以那些编译器优化者故作聪明,把a读进CPU的cache里,像上面的代码,假如a在赋值期间没有被改变,就直接从CPU的cache里取a的副本来进行赋值。但是bug也显而易见,当a在赋给b之后,可能a已经被另一个线程改变而重新写回了内存,但这个线程并不知道,依旧按照原来的计划从CPU的cache里读a的副本进来赋值给c,结果不幸发生了。
            于是编译器的开发者为了补救这一bug,提供了一个Volatile让开发人员为他们的过失埋单,或者说提供给开发人员了一个选择效率的权利。当变量加上了Volatile时,编译器就老老实实的每次都从内存中读取这个变量值,否则就还按照优化的方案从cache里读。

    volatile的本意是一般有两种说法--1.“暂态的”;2.“易变的”。
    这两种说法都有可行。但是究竟volatile是什么意思,现举例说明(以Keil-c与a51为例
    例子来自Keil FQA),看完例子后你应该明白volatile的意思了,如果还不明白,那只好
    再看一遍了。


    例1.

    void main (void)
    {
          volatile int i;
          int j;

          i = 1;    //1    不被优化 i=1
          i = 2;    //2    不被优化 i=1
          i = 3;    //3    不被优化 i=1

          j = 1;    //4    被优化
          j = 2;    //5    被优化
          j = 3;    //6    j = 3
    }
    ---------------------------------------------------------------------
    例2.

    函数:

    void func (void)
    {
          unsigned char xdata xdata_junk;
          unsigned char xdata *p = &xdata_junk;
          unsigned char t1, t2;

          t1 = *p;
          t2 = *p;
    }

    编译的汇编为:

    0000 7E00      R       MOV       R6,#HIGH xdata_junk
    0002 7F00      R       MOV       R7,#LOW xdata_junk
    ;---- Variable 'p' assigned to Register 'R6/R7' ----

    0004 8F82            MOV       DPL,R7
    0006 8E83            MOV       DPH,R6

    ;!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 注意
    0008 E0              MOVX      A,@DPTR
    0009 F500      R       MOV       t1,A

    000B F500      R       MOV       t2,A
    ;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    000D 22              RET    

    将函数变为:
    void func (void)
    {
          volatile unsigned char xdata xdata_junk;
          volatile unsigned char xdata *p = &xdata_junk;
          unsigned char t1, t2;

          t1 = *p;
          t2 = *p;
    }

    编译的汇编为:
    0000 7E00      R       MOV       R6,#HIGH xdata_junk
    0002 7F00      R       MOV       R7,#LOW xdata_junk
    ;---- Variable 'p' assigned to Register 'R6/R7' ----

    0004 8F82            MOV       DPL,R7
    0006 8E83            MOV       DPH,R6

    ;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    0008 E0              MOVX      A,@DPTR
    0009 F500      R       MOV       t1,A          ;a处

    000B E0              MOVX      A,@DPTR
    000C F500      R       MOV       t2,A
    ;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    000E 22              RET    


    比较结果可以看出来,未用volatile关键字时,只从*p所指的地址读一次
    如在a处*p的内容有变化,则t2得到的则不是真正*p的内容。

    ---------------------------------------------------------------------
    例3


    volatile unsigned char bdata var;    // use volatile keyword here
    sbit var_0 = var^0;
    sbit var_1 = var^1;
    unsigned char xdata values[10];

    void main (void)    {
        unsigned char i;

        for (i = 0; i < sizeof (values); i++)    {
          var = values[i];
          if (var_0)    {
            var_1 = 1;      //a处
                 
            values[i] = var;    // without the volatile keyword, the compiler
                              // assumes that 'var' is unmodified and does not
                              // reload the variable content.
          }
        }
    }


    在此例中,如在a处到下一句运行前,var如有变化则不会,如var=0xff; 则在
    values[i] = var;得到的还是values[i] = 1;

    ---------------------------------------------------------------------
    应用举例:

    例1.
    #define DBYTE ((unsigned char volatile data    *) 0)

    说明:此处不用volatile关键字,可能得不到真正的内容。
    ---------------------------------------------------------------------

    例2.


    #define TEST_VOLATILE_C

    //***************************************************************
    // verwendete Include Dateien
    //***************************************************************
    #if __C51__ < 600
        #error: !! Keil 版本不正确
    #endif

    //***************************************************************
    // 函数 void v_IntOccured(void)
    //***************************************************************
    extern void v_IntOccured(void);

    //***************************************************************
    // 变量定义
    //***************************************************************
    char xdata cValue1;            //全局xdata
    char volatile xdata cValue2; //全局xdata

    //***************************************************************
    // 函数: v_ExtInt0()
    // 版本:
    // 参数:
    // 用途:cValue1++,cValue2++
    //***************************************************************
    void v_ExtInt0(void) interrupt 0 {
        cValue1++;
        cValue2++;
    }       

    //***************************************************************
    // 函数: main()
    // 版本:
    // 参数:
    // 用途:测试volatile
    //***************************************************************

    void main() {
    char cErg;

    //1. 使cErg=cValue1;
    cErg = cValue1;

    //2. 在此处仿真时手动产生中断INT0,使cValue1++; cValue2++
    if (cValue1 != cErg)
        v_IntOccured();

    //3. 使cErg=cValue2;
    cErg = cValue2;

    //4. 在此处仿真时手动产生中断INT0,使cValue1++; cValue2++
    if (cValue2 != cErg)
        v_IntOccured();
     
    //5. 完成
        while (1);
    }

    //***************************************************************
    // 函数: v_IntOccured()
    // 版本:
    // 参数:
    // 用途: 死循环
    //***************************************************************
    void v_IntOccured() {
        while(1);
    }


    仿真可以看出,在没有用volatile时,即2处,程序不能进入v_IntOccured();
    但在4处可以进入v_IntOccured();

    volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。

    例如:
    volatile int i=10;
    int j = i;
    ...
    int k = i;

    volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。

    而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。

    /**********************

    一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
    1) 并行设备的硬件寄存器(如:状态寄存器)
    2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3) 多线程应用中被几个任务共享的变量
    回答不出这个问题的人是不会被雇佣的。我认为这是区分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;
    }

    位操作(Bit manipulation)

    //*********************

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

    一:告诉compiler不能做任何优化
       比如要往某一地址送两指令:
       int *ip =...; //设备地址
       *ip = 1; //第一个指令
       *ip = 2; //第二个指令
       以上程序compiler可能做优化而成:
       int *ip = ...;
       *ip = 2;
       结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:
       volatile int *ip = ...;
       *ip = 1;
       *ip = 2;
       即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。
    二:表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。

       如   volatile char a;   
            a=0;
           while(!a){
    //do some things;   
           }   
           doother();
       如果没有 volatile doother()不会被执行


  • 相关阅读:
    Django的model form
    Django之验证码
    ajax获取跨域数据
    js+css模仿打字效果
    CSS自定义消息提示
    CSS画各种二维图形
    最简单的动态进度条
    利用javascript(自定义事件)记录尺寸可变元素的尺寸变化过程
    超级简单的利用javascript实现文件拖拽事件
    javascript 公历与农历相互转换工具类
  • 原文地址:https://www.cnblogs.com/shelvenn/p/1035193.html
Copyright © 2011-2022 走看看