很多人对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=1j = 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,@DPTR0009 F500 R MOV t1,A000B 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_junk0002 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,@DPTR0009 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 heresbit 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; //全局xdatachar 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这个关键字,在网上查了下他的用法可以归结为以下两点:
如 volatile char a; a=0; while(!a){ //do some things; } doother(); 如果没有 volatile doother()不会被执行