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. C# 代码转换到Python

    下载地址:http://pan.baidu.com/s/1dDAZJxv SharpDevelop 3.1 now supports converting C# and VB.NET code to ...

  2. VMware Network Adapter VMnet1和VMnet8 未识别的网络的解决方法

    VMware Network Adapter VMnet1和VMnet8 被防火墙认定为未识别的网络,阻隔,无法使用端口映射,虚拟机的80端口无法传入,数据包只能出不能入.且公用网络被限制不能修改为家 ...

  3. eclipse,tomcat部署web项目,以及本地文件访问

    1.直接把项目复制到Tomcat安装目录的webapps目录中,这是最简单的一种Tomcat项目部署的方法,也是初学者最常用的方法. 2.在tomcat安装目录中有一个conf文件夹,打开此文件夹,其 ...

  4. 关于我们-EIBOA易博男装-互联网品质男装品牌-在线销售男士西服,衬衫,外套,西裤,领带|全场免运费,30天退换货保障

    关于我们 - 网站底部 | ELLE中国 | ELLE China 关于赫斯特国际集团 男人尚--专注男人时尚 关于男人尚 这些数据,你知道吗? 1.全国至少1亿男人的衬衫尺码错误: 2.57%的男人 ...

  5. CSS多级数字序号的目录列表(类似3.3.1.这样的列表序号)

    编写文档手册的时候,我们经常需要列表项前面的序号将上级各层的序号也附加在前面,如下图: (图一)  但默认的<ol>列表,任何层次都是单个序号开始.如下图: (图二) 要实现图一效果,方法 ...

  6. 【转】修改Android工程的名称、安装路径

    申明:转载自:http://blog.csdn.net/hcj116/article/details/21712353 在Google提供的Eclipse集成开发环境adt-bundle下修改名称的总 ...

  7. Struts分页

    1.分页的bean类PaginationSupport.java  2.写好后直接在action里面调用,计算当前页显示的数据  3.写一个公用的jsp页面,直接在需要分页的页面include就可以了 ...

  8. qt tablewidget中单个和批量删除代码如下(部分)截图如下

    def coltable(self):#行删除    row=self.downwidget.currentRow()    select=self.downwidget.isItemSelected ...

  9. .NET--接口设计

    我们学习.net视频的时候,老师讲的是"介面设计",有意思的是,这里的介面不是我们想象中的界面的意思,而是接口的意思. 由于视频是Micorsoft公司做的,所以整个视频看下来.仅 ...

  10. poj 3009 Curling 2.0 (dfs )

    Curling 2.0 Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11879   Accepted: 5028 Desc ...