C语言应用笔记(四):C语言volatile关键字及其使用

在 C 语言中,还有一个并不常用但却非常有用的关键字 volatile。volatile 总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用 volatile 关键字禁止做这些优化。

volatile 的本意是“易变的” ,因为访问寄存器要比访问内存单元快的多,所以编译器一般都会做减少存取内存的优化,但有可能会读错数据。当要求使用 volatile 声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

切确的说就是:遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用 valatile,则编译器将对所声明的语句进行优化。

简单的说就是:volatile 关键词影响编译器编译的结果,用 volatile 声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错。

XBYTE[2] = 0x55;
XBYTE[2] = 0x56;
XBYTE[2] = 0x57;
XBYTE[2] = 0x58;

对外部硬件而言,上述四条语句分别代表不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有 XBYTE[2]=0x58 (即忽略前三条语句,只产生一条机器代码),如果使用 volatile 关键字修饰,则编译器会逐一地进行编译并产生相应的机器代码(四条机器代码)。

 

那么,在什么情况下使用 volatile 关键字呢?一般而言,以下几种情况通常会使用 volatile 关键字:

a. 在中断服务程序中修改的、供其他程序检测的变量,通常需要加 volatile;

b. 在多任务环境下,各任务间共享的标志,通常也需要加 volatile;

c. 存储器映射的硬件寄存器通常也需要加 volatile,因为每次对它的读写都可能有不同的意义。

 

(1)使用中断改变对象值的情况

我们在中断服务程序中操作了某个对象,例如改变了一个变量的值,而在其他程序中我们希望通过这个变量值的改变去进行一些操作。在这个时候,我们使用和不使用 volatile 关键字,可能会得到截然不同的结果。

如果我们将变量定义为:

static int var=0;

然后在中断服务程序中将其修改为:

var = 1;

但是在另一程序中使用它但不会修改它,那么编译器会认为它没有变化,从而直接使用寄存器中的副本。

如果我们将变量定义为:

volatile int var=0;

那么结果就完全不同了,每次在函数中使用 var 变量时,都会重新读取,而不会使用寄存器中的副本。

 

(2)多任务共享标志的情况

多任务共享时,其实也是一样的,在一个任务中修改了一个对象后,在另一个对象中应用它,但是编译器并没有发现在该任务中对它有修改。由于访问寄存器的速度比 RAM 要快,所以编译器一般都会做减少存取外部RAM的优化,从而并不能响应在另一任务下的变化。

在这种情况下,一般都需要使用 volatile 关键字来修饰任务间共享的标识。

 

(3)硬件寄存器内存映射的情况

内存映射的情况是非常多的,比如我们将外设的寄存器映射到内存的某一段区域;再比如我们通过 DMA 方式将数据直接写到内存中的某一段区域。

由于外部的数据都是直接进入内存区域,而程序中需要引用它的值。编译器优化时可能就直接采用寄存器中的副本了。显然这不是我们需要的结果,这时将这段内存对应的变量使用 volatile 关键字修饰,则可保证我们的需求。

 

volatile的本质:

a. 编译器的优化

在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值 copy 到该寄存器中,以便保持一致。

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

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

b. volatile 应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。

 

综上所述,频繁地使用 volatile 很可能会增加代码尺寸和降低性能,但它却可以保证程序的正确性,所以在合适的地方使用 volatile 关键字是必要的。

 

欢迎关注微信公众号『OpenSSR』

你可能感兴趣的:(C语言)