1. 什么是volatile关键字?
在C语言中,volatile是一种类型修饰符,用于告诉编译器,尤其是优化器,相应的变量是易变的,需要特殊对待。在C语言中,变量频繁地被访问和修改,因此这些变量的值可能会被缓存到寄存器中,而非内存中。这种情况下,volatile关键字的作用就显现出来了。
2. volatile关键字的作用是什么?
2.1 防止编译器优化
一些特殊的变量,如外设寄存器等,其值经常会被改变。如果不告诉编译器这些变量是易变的,那么编译器就有可能将其优化掉,从而导致程序运行出错。因此需要用volatile关键字来告诉编译器这些变量的值不能被优化掉。
int flag = 0;
while(flag == 0){}
如果把flag这个变量去掉volatile修饰,编译器就会按照自己的优化方式,将该变量缓存在CPU寄存器中,等待flag改变状态。所以,在这种情况下,while循环就成为了一个死循环,程序无法退出。代码如下:
#include<stdio.h>
int main(){
int flag = 0;
while(flag == 0){}
printf("Hello world!");
return 0;
}
用GCC编译器编译后,程序无法退出。
而加入volatile后,编译器就不会做这样的优化,每次都会在内存中进行读取,从而获得最新变量值,程序可以正常执行结束。代码如下:
#include<stdio.h>
volatile int flag = 0;
int main(){
while(flag == 0){}
printf("Hello world!");
return 0;
}
2.2 防止编译器指令重排
编译器对代码进行优化时,往往会根据自己的判断优化指令的先后执行顺序,来提升程序的执行速度。然而,这种指令重排有可能会影响程序的正确性。对于一些涉及多线程的编程场景,volatile关键字的作用就体现出来了。因为多线程的执行顺序是无法控制的,因此需要volatile关键字来阻止编译器的指令重排。
volatile int flag = 0;
int value = 0;
void foo(){
value = 42;
flag = 1;
}
void bar(){
if(flag){
printf("value=%d\n", value);
}
}
int main(){
foo();
bar();
return 0;
}
如果把flag和value这两个变量去掉volatile修饰,编译器就有可能把赋值操作和flag的赋值操作交换位置,导致bar函数输出value的值为0。因此,在这种情况下,需要使用volatile关键字来保证程序的正确性。
2.3 防止编译器从寄存器中读取变量的值
对于一些涉及硬件设备IO的程序,需要从设备中读取特定的值。如果从IO设备中读取的这些值只存储在硬件寄存器中,而不是存储在内存中,则编译器会从寄存器中直接读取这些值,而无视IO设备。而volatile关键字可以解决这个问题,它告诉编译器每次操作这些变量时都必须从内存中读取。
2.4 总结
综上所述,volatile关键字的作用主要有三个:防止编译器进行优化、防止编译器指令重排和防止编译器从寄存器中读取变量的值。在一些特殊的编程场景下,如多线程编程、硬件设备IO等场景中,使用volatile关键字可以有效保证程序的正确性。但是,在一般的编程场景下,不要滥用volatile关键字,因为它会导致程序的性能降低。