volatile的用法
还记得上一篇在介绍gcc的参数的时候有个超级难搞的-O参数吧。这个参数是设置编译优化的级别的。编译优化的一个假设是我们写的程序会贪婪无度的使用机器的资源,因此编译器会优化汇编代码,提升执行效率。但是带来的代价是可能会出现难以发现的逻辑错误。CPU是从寄存器中取出变量的值,寄存器中的值是从内存中读取出来的。正常情况下内存中的值发生了修改,把值拷贝到寄存器中。但是有可能发生这样一种情况,即内存中的值发生了改变,但寄存器中的副本没有被修改。原因就是编译器优化的时候,没有每次都从内存中读取值到寄存器,而是直接取了当前寄存器中值。看下面的例子:
static
int
i = 0;
int
main(
void
)
{
//...
while
(1)
{
if
(i)
dosomething();
}
}
/*Interruptserviceroutine.*/
void
ISR_2(
void
)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
再来一个更有趣的例子
#include<stdio.h>
void
main(
int
argc,
char
*argv[])
{
int
i = 10;
int
a = i;
printf
(
"i=%d"
,a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm
{
mov dword ptr[ebp-4],20h
}
int
b = i;
printf
(
"i=%d"
,b);
}
输出结果如下:
i = 10
i = 10
#include<stdio.h>
void
main(
int
argc,
char
*argv[])
{
volatile
int
i = 10;
int
a = i;
printf
(
"i=%d"
,a);
__asm
{
` mov dword ptr[ebp-4],20h
}
int
b = i;
printf
(
"i=%d"
,b);
}
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
volatile对应的变量可能在你的程序本身不知道的情况下发生改变,比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量。你自己的程序,是无法判定何时这个变量会发生变化。还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。