原创 C语言中volatile关键字的作用

2011-5-16 15:33 2301 7 7 分类: MCU/ 嵌入式

  A variable is declared as volatile whenever its value can be changed by something outside the control of the code in which it appears. For example, peripheral registers can be changed by the hardware itself or within an interrupt. If volatile is not specified, then it is assumed the variable can only be modified by the code in which it appears and the compiler may optimize out what is seen as an unnecessary access. The compiler will not, however, optimize out any volatile variable access; this is true even if the compiler's optimizer is enabled.  ------摘自TI数据手册

 一个定义为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可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

int square(volatile int *ptr) 


int a; 

a = *ptr; 

return a * a; 

关键在于两个地方: 

1. 编译器的优化 (请高手帮我看看下面的理解) 

在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;

当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致

当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致 

当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致 

2. 在什么情况下会出现

1). 并行设备的硬件寄存器(如:状态寄存器) 

2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 

3). 多线程应用中被几个任务共享的变量 

volatile对应的变量可能在你的程序本身不知道的情况下发生改变,比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量.  你自己的程序,是无法判定合适这个变量会发生变化.  还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道. 对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。 

典型的例子 

 

for ( int i=0; i<100000; i++); 

这个语句用来测试空循环的速度的,

但是编译器肯定要把它优化掉,根本就不执行 

如果你写成 

for ( volatile int i=0; i<100000; i++); 

它就会执行了 

 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中则只能依靠硬件的良好设计了。 

文章评论0条评论)

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