高中生制服:深入浅出理解和掌握C语言中volatile关键字

来源:百度文库 编辑:偶看新闻 时间:2024/04/29 16:01:34

(一)volatile的解释

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。

用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。

没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取值(因为之前i被访问过,也就是说之前就从内存中取出i的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的结果(访问cpu寄存器比访问ram快的多)

以上两种情况的区别在于被编译成汇编代码之后,两者是不一样的。之所以这样做是因为变量i可能会经常变化,保证对特殊地址的稳定访问。

(二)通过反汇编方法深入理解volatile

以上我们可以通过反汇编的方法看到不同之处。

函数:
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的内容。
(三)应用举例(注意VC环境下debug模式和release模式是不一样的)

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是一个寄存器变量或者表示一个端口数据就容易出错,所以说vola

tile可以保证对特殊地址的稳定访问。
  注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面

通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
  首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的

代码:
 
#i nclude
void main()
{
 int i=10;
 int a = i;
 
 printf("i= %d\n",a);
 //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
 __asm {
  mov dword ptr [ebp-4], 20h
 }
 
 int b = i;
 printf("i= %d\n",b);
    
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。

下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#i nclude
void main()
{
 volatile int i=10;
 int a = i;
 
 printf("i= %d\n",a);
 __asm {
  mov dword ptr [ebp-4], 20h
 }
 
 int b = i;
 printf("i= %d\n",b);
    
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!

(四)volatile的深度理解和深入应用场景

volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用: 
1 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器 自己无法知道,volatile就是告诉编译器这种情况。 
2 不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) ...

if的条件不会当作无条件真。 
3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作,然而对Memory Mapped IO的处理是不能这样优化的。
应用场景

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义