volatile关键字用于阻止编译器进行相关优化。编译时如果启用了编译选项,编译器会根据其自身的逻辑判断,优化代码,包括增减语句、调整语句顺序等,而这在很多特定的情况下是不被接受的。比如,读取GPIO端口值或者寄存器值,一般要求读取的值就是GPIO端口或者寄存器内当前的值。如果这些读取的语句不幸被编译器优化了,那么读取的值就有可能是一个过期的值,而不是当前状态下的真正的值。另一个比较常见的应用就是资源引用计数器,该计数器通常就被定义为一个使用了volatile关键字限定的整数,以保证每次读取的该整数的值都是最新的值。
考虑如下代码:
#include <stdio.h>
int main(int argc, char** argv) {
volatile int i = 10;
asm ("movl $20, %0;"
:
: "m"(i)
);
printf("i: %i/n", i);
return 0;
}
假定该段代码存放在文件test.c中,使用如下命令编译该文件:
gcc -O -o test test.c
这里选项-O用于打开gcc优化功能。编译完成之后,运行命令./test,输出结果为
i: 20
这符合我们的期望,这表明main函数中的内嵌汇编语句生效了。而如果我们把main函数中i申明语句中的volatile语句删除,重复上述编译、运行动作之后,输出的结果则为:
i: 10
至此,volatile的作用体现出来了。它可以阻止一切与其有关的优化,即便编译器打开了编译选项。值得一提的是这里的内嵌汇编,它的作用是向编译器隐瞒i变量被修改的事实(内嵌汇编语句修改了i的值),从而使得在编译器打开优化选项的时候,会尝试对那些没有volatile语句限定的变量及其相关语句进行优化。此外,内嵌汇编中i放在输入操作数部分而没有放在输出操作数部分也是出于这一目的。倘若放在输出部分,编译器便会识破我们的伎俩,知道我们随后修改了i的值,进而会规避此处的优化,此时两种情况下的输出值都将一样。
另外volatitle和可以用来阻止编译器对内嵌汇编的优化,仅需在asm和左括号之间插上关键字volatile即可。