volatile用于防止相关变量被优化

  例如对外部寄存器的读写。对有些外部设备的寄存器来说,读写操作可能都会引发一定硬件操作,但是如果不加volatile,编译器会把这些寄存器作为普通变量处理,例如连续多次的对同一地址写入,会被优化为只有最后一次的写入。实际上,网卡的数据发送,就是按顺序连续往一个同地址写入数据,如果被优化,网卡将不能正常驱动。对于外部寄存器的读写,经常用 XBYTE,其实你看一下XBYTE的原型就知道了,里面也是有个volatile的。
  另一个使用场合是中断。如果一个全局变量,在中断函数和普通函数里都用到过,那最好对这个变量加volatile修饰。否则普通函数里,可能会仅从寄存器里读取这个变量以便加快速度,而不去实际地址读取该变量。

  很多人对Volatile都不太了解,其实Volatile是由于编译器优化所造成的一个Bug而引入的关键字。

;
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    不被优化 i=1
      i = ;    //2    不被优化 i=1
      i = ;    //3    不被优化 i=1

      j = ;    //4    被优化
      j = ;    //5    被优化
      j = ;    //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;
} 

  编译的汇编为:

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

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

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

000B F500      R       MOV       t2,A
;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                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;
} 

  编译的汇编为:

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

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

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

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

                RET 

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

volatile unsigned char bdata var;    // use volatile keyword here
sbit var_0 = ;
sbit var_1 = ;
unsigned ];

void main (void)
{
    unsigned char i;

    ; i < sizeof (values); i++)
    {
        var = values[i];
        if (var_0)
        {
            var_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++
//***************************************************************

{
    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. 完成
    );
}

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

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

KEIL里 Volatile的用法的更多相关文章

  1. volatile的用法

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  2. C#中volatile的用法

    恐怕比较一下volatile和synchronized的不同是最容易解释清楚的.volatile是变量修饰符,而synchronized则作用于一段代码或方法:看如下三句get代码: int i1;  ...

  3. Hibernate(或其它ORM)里的inverse用法详解,内容摘自Java web轻量级开发面试教程

    本文来是从 java web轻量级开发面试教程从摘录的. Inverse的英文含义是反转,在Hibernate中用来决定是由哪方来维护两个业务实体类之间的关联关系,具体而言,就是由哪方去设置这个被外键 ...

  4. Linux编程里getopt_long_only函数用法详解

    在程序中难免需要使用命令行选项,可以选择自己解析命令行选项,但是有现成的,何必再造轮子.下面介绍使用getopt_long_only和getopt_long(两者用法差不多)解析命令行选项. 程序中主 ...

  5. ABAP里SELECT的用法汇总(转)

    通常使用Open SQL的数据查询语句SELECT将数据库条目选择到内存.一.SELECT语句:1)SELECT用于确定读取数据表中的哪些字段:2)FROM子句用于确定从哪些内表或者视图中读取数据:3 ...

  6. keil里面的包

    1.使用Keil开发STM32程序时,需要下载STM32固件包 首先,keil就是一个编辑器,就跟记事本一样. 但keil还可以编译链接程序. 在创建一个工程时,要选择芯片序列. 要选择芯片序列的原因 ...

  7. Java volatile的用法---转载

    我们知道,在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步. 这在JVM 1.2之前,Java的内存模型实现总是从主 ...

  8. 【译】css动画里的steps()用法详解

    原文地址:http://designmodo.com/steps-c... 原文作者:Joni Trythall 我想你在css 动画里使用steps()会和我一样有很多困惑.一开始我不清楚怎样使用它 ...

  9. SQL里IN的用法以及优化

    1.in后条件不多,可以考虑主表建索引,或用union all 代替 2. in 和 exists的区别: 如果子查询得出的结果集记录较少,主查询中的表较大且又有索引时应该用in, 反之如果外层的主查 ...

随机推荐

  1. CentOs6.5中安装和配置vsftp简明

    这篇文章主要介绍了CentOs6.5中安装和配置vsftp简明教程,需要的朋友可以参考下     一.vsftp安装篇 复制代码代码如下: # 安装vsftpdyum -y install vsftp ...

  2. github如何删除一个repository(仓库)

    昨天有人问我如何删除自己创建的一个项目,我浏览了一下github网站,确实不太容易找到删除功能.这里介绍一下啊如何在github上删除一个repository 一:首先点击进入你要删除的reposit ...

  3. poj2689:素数筛

    题目大意,给定l和u,求区间[l,u]内的素数中,相邻两个差最大和最小的素数其中 u的范围达到了2e9本质上需要找出n以内的所有素数,使用筛法.先保存50000(大于sqrt(2e9))内的所有素数, ...

  4. Linux更换python版本 (转载)

    安装完CentOS6.5(Final)后,执行#Python与#python -V,看到版本号是2.6,而且之前写的都是跑在python3.X上面的,3.X和2.X有很多不同,有兴趣的朋友可以参考下这 ...

  5. 第32讲 UI组件之 时间日期控件DatePicker和TimePicker

    第32讲 UI组件之 时间日期控件DatePicker和TimePicker 在Android中,时间日期控件相对来说还是比较丰富的.其中, DatePicker用来实现日期输入设置,    Time ...

  6. 论 <%@taglib prefix="s" uri="/struts-tags" %> 的重要性

    前段时间在做项目的时候,碰到这个问题 结果是相应的内容显示不出来,原来是忘了这句很关键的引入:<%@taglib prefix="s" uri="/struts-t ...

  7. tab切换jquery代码

    http://immmmm.com/jquery-tab-switch-code-improved.html html <div id="sidebar-tab">   ...

  8. 浏览器内核Trident/Gecko/WebKit/Presto

    “浏览器内核”主要指渲染引擎(Rendering Engine),负责解析网页语法(如HTML.JavaScript)并渲染.展示网页.因此,所谓的浏览器内核通常也就是指浏览器所采用的渲染引擎, 渲染 ...

  9. (LeetCode)两个队列来实现一个栈

    原题例如以下: Implement the following operations of a stack using queues. push(x) -- Push element x onto s ...

  10. android 银行卡。。空格输入

    class myWatcher implements TextWatcher { int beforeTextLength = 0; int onTextLength = 0; boolean isC ...