读音:vaoletail
C语言中的volatile
关键字是一个重要的类型修饰符,它用于声明一个变量具有“易变性”,即可能在编译器无法察觉的情况下被改变其值。Volatile意思是“易变的”,应该解释为“直接存取原始内存地址”比较合适。 “易变”是因为外在因素引起的,像多线程,中断等。
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
总结:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错。
编译器优化介绍:
由于内存访问速度远不及CPU处理速度,为提高机器整体性能。
1)在硬件上: 引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。
2)软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器。
由于访问寄存器要比访问内存单元快的多,编译器在存取变量时,为提高存取速度,编译器优化有时会先把变量读取到一个寄存器中;以后再取变量值时就直接从寄存器中取值。但在很多情况下会读取到脏数据,严重影响程序的运行效果。
一般说来,volatile用在如下的几个地方:
(1)中断服务程序中修改的供其它程序检测的变量,需要加volatile:当变量在触发某中断程序中修改,而编译器判断主函数里面没有修改该变量,因此可能只执行一次从内存到某寄存器的读操作,而后每次只会从该寄存器中读取变量副本,使得中断程序的操作被短路。
(2)多线程环境:在多线程编程中,如果多个线程共享并修改某个变量,而该变量的改变不受当前执行线程控制(比如由其他线程、中断服务程序或者硬件本身修改),那么这个变量就应该用volatile来修饰,以保证所有线程都能看到最新更新的值。
(3)硬件访问:与硬件交互时,例如访问状态寄存器或其他硬件映射的内存位置,这些位置的内容可能由硬件设备自行更改,而不通过CPU指令直接操作。在这种情况下,硬件寄存器通常需要被声明为volatile,这样每次访问都会得到最新的硬件状态,而非缓存在寄存器中的旧值。
注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
一个函数参数可以同时被声明const和volatile。当一个参数既被声明为const又为volatile时,它意味着该参数在函数执行期间不可被修改(通过该函数),但其值可能在任何时间点被硬件、操作系统或其他并发线程等不受当前程序控制的因素改变。
例如,在嵌入式系统中,一个指向设备状态寄存器的指针作为函数参数时,它可以被声明为volatile类型。
void checkDeviceStatus(volatile const uint32_t* statusRegister)
{// 在此函数中,我们不能修改statusRegister指向的内容// 但是我们必须每次都从内存中读取它的实际值,因为它可能随时由外部硬件更改。if (*statusRegister & DEVICE_STATUS_FLAG) {// 处理设备状态}
}
volatile
关键字确保编译器不会对statusRegister
的内容进行优化,即使多次读取也会每次都直接从内存中读取;而const
关键字则禁止函数体内部尝试修改这个寄存器的内容。
2). 一个指针可以是volatile 吗?解释为什么。
一个指针不仅可以是volatile
,而且在某些情况下必须这样声明。声明为volatile
的指针表示其指向的数据可能会被程序外部因素异步更改,即使没有显式的修改语句。
例如,在多线程环境或与硬件交互的场合,某个全局变量可能是通过一个I/O端口或者中断服务程序来更新的,那么指向这个变量的指针就应该声明为volatile。
volatile int *portValue = (volatile int*)0x1000; // 假设0x1000是硬件端口地址// 线程A中读取并处理端口值
while (1) {process(*portValue);
}// 线程B中或中断服务例程中端口值被硬件改写
ISR() {*portValue = readHardwarePort();
}
volatile
修饰符告诉编译器不要对portValue
指向的内存位置进行优化,每次访问都需要重新读取内存中的实际值,因为硬件可能会在任何时候更新这个值。
????这个部分没看懂。
int main(void){int i;i = 1;i = 2;return i;
}
在Debug模式(无任何优化)下, 生成的32位汇编代码(省略其它代码)为:
mov dword ptr[ebp-4],1
mov dword ptr[ebp-4],2
mov eax,dword ptr[ebp-4]
ret
可见, 对i的每次赋值都会产生一条汇编语句来对应, 这完全同C程序意思,而当生成模式改成Release(优化被打开; 可能需要在项目设置中钩上"生成调试信息")后,对应如下(省略其它代码)。
mov eax,2
ret
多余的赋值代码都被优化掉了, 这就是编译器的优化措施。把源代码改成如下形式:
int main(void){volatile int i;i = 1;i = 2;return i;
}
修改后, Debug生成的汇编代码没有变化, 但Release生成的代码却变成了:
push ecxmov dword ptr[esp],1mov dword ptr[esp],2mov eax,dword ptr[esp]pop ecx
可见, 加了volatile后, 就算打开优化, 编译器也不会作优化, 每次取值都是重新取值。
参考:
https://www.cnblogs.com/hjh-666/p/11148119.html
https://www.cnblogs.com/memset/archive/2012/12/19/2825530.html
https://www.cnblogs.com/armlinux/archive/2010/09/14/2396918.html