“不妨大胆一些,爱一个人,攀一座山,追一个梦。”----- 大鱼海棠
volatile是一个类型修饰符(type specifier), 防止编译器对代码进行优化。
volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。 volatile应该解释为“直接存取原始内存地址”比较合适。
编译器的优化:
在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时,有时会先把变量读取到一个寄存器中;以后再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致。但是,当变量被别的线程改变时,该寄存器的值不会相应地改变,从而造成应用程序读取的值和实际的变量值不一致。
例子:
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一地进行编译并产生相应的机器代码(产生四条代码)。
精确地说就是,编译器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
使用volatile变量的几个case:
1)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
2)多线程应用中被几个任务共享的变量
这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。
例子:
volatile int i=10;
int a=i;
//...
//其他代码,并未明确告诉编译器,对i进行过操作
Int b = i;
volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。
例子:
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也应该如此说明。
Ref: https://baike.baidu.com/item/volatile/10606957?fr=aladdin
Ref:
https://www.cnblogs.com/reality-soul/p/6140192.html
https://www.cnblogs.com/yangguang-it/p/6719261.html?utm_source=itdadao&utm_medium=referral