volatile
volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,编译器对访问该变量的代码就不再进行优化,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。
由上分析,volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。
举几个例子
1、(禁止优化作用)
unsigned int *ip = 0x12345678;
*ip = 1;
*ip = 2;
这个程序则会被系统优化为:
unsigned int *ip = 0x12345678;
*ip = 2;
这可能在信号传递或者时序的操作的时候会发生错误
如果ip定义为volatile则不会进行任何优化。
2、(用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份)
实际上这也是优化的一种,每次读取数据进内存进行读取(速度减慢)。这就是volatile不能过度使用的原因。
例如以下中断程序:
int i=0;
int main(void) {
...
while (1){
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void){
i=1;
}
所以总结volatile经常应用的场景为:
- 中断服务程序中修改的供其它程序检测的变量需要加volatile(原因2)
- 多任务环境下(如多线程)各任务间共享的标志应该加volatile(原因2)
- 存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义(原因1)
例如:假设要对一个设备进行初始化,此设备的某一个寄存器为0x12345678,发送一个锯齿波。
int *output = (unsigned int *)0x12345678;//定义一个IO端口;
int init(void){
int i;
for(i=0;i< 10;i++){
*output = i;
}
}
经过编译器优化后,编译器认为前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,所以编译器最后给你编译编译的代码结果相当于:
int *output = (unsigned int *)0x12345678;//定义一个IO端口;
int init(void){
*output = 9;
}
所以解决方法为加上一个volatile关键字,禁止程序进行优化。
volatile int *output=(volatile unsigned int *)0x12345678;//定义一个I/O端口
总结:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错
常见问题
一个参数既可以是const还可以是volatile吗?
可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
一个指针可以是volatile 吗?
可以,可以把指针看成一个变量,和变量的实质是一样的。
例子
int square(volatile int *ptr){
return *ptr * *ptr;
}
这个函数要完成的是返回ptr指向的变量的平方,可以看出volatile属实多此一举,这是由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr){
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
在运行过程中由于是volatile变量,ptr会意想不到的改变,因此a和b可能是不同的。所以如果一定要输入volatile变量的话可以这么做:
long square(volatile int *ptr){
int a;
a = *ptr;
return a * a;
}
可以看出使用volatile还会增加代码的长度,所以不能滥用volatile。