原创 volatile的用法

2008-5-2 18:04 2582 10 10 分类: MCU/ 嵌入式

volatile的本意是“易变的”

由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:

static int i="0";

int main(void)
{
...
while (1)
{
if (i) dosomething();
}
}

/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}

程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。

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

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile;

3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。


 简单点: 就是该变量会以编译器无法预知的方式发生变化,请编译器不要做优化(所有的编译器的优化均假设编译器知道变量的变化规律)


 


 


============================================


 


C 关键字 volatile



C 关键字 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有什么含意?并给出三个不同的例子。


一个定义为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的值可能被意想不到地该变,因此ab可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
     long square(volatile int *ptr)
     {
            int a;
            a = *ptr;
            return a * a;
     }


 


= = = = = = = = = = = = = = = = 实例讲解= = = = = = = = = = = = = = =


volatile的本意是一般有两种说法--1.“暂态的2.“易变的
这两种说法都有可行。但是究竟volatile是什么意思,现举例说明(以Keil-ca51为例
例子来自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;
    if (var_0)  {
      var_1 = 1; //a

       
      values = var;  // without the volatile keyword, the compiler
                        // assumes that 'var' is unmodified and does not
                        // reload the variable content.
    }
  }
}


在此例中,如在a处到下一句运行前,var如有变化则不会,如var=0xff; 则在
values = var;
得到的还是values = 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();

文章评论0条评论)

登录后参与讨论
我要评论
0
10
关闭 站长推荐上一条 /2 下一条