在百度百科中volatile关键字是这样解释的:
volatile是一个特征修饰符(type specifier)。volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。一般在以下场景中使用:
- 并行设备的硬件寄存器(如:状态寄存器)
- 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
- 多线程应用中被几个任务共享的变量
仅看上述内容总感觉让人不够直观,与其绞尽脑汁去细品,不如直接写段代码,看看无volatile和带volatile的变量在编译后生成的汇编代码有哪些区别。
因为笔者相对熟悉arm平台,所以选用了armcc编译器,版本为:ARM Compiler 5.06 update 6 (build 750),优化等级为O3,操作系统平台为Windows 10.0.18362,具体编译命令如下:
armcc.exe -O3 -S D:volatile测试volatile.c -o D:volatile测试volatile.s
无volatile代码如下:
int a=1; int b=2; int c; int main() { b=a; c=b; return 0; }
使用volatile修饰变量b,修改后的代码(带volatile)如下:
int a=1; volatile int b=2; int c; int main() { b=a; c=b; return 0; }
无volatile代码生成的关键汇编(部分)如下:
main PROC LDR r0,|L0.24| LDR r1,[r0,#0] ; 将变量a载入到寄存器r1 STR r1,[r0,#4] ; 将寄存器r1存储至变量b STR r1,[r0,#8] ; 将寄存器r1存储至变量c MOV r0,#0 BX lr ENDP
而带volatile代码生成的汇编如下:
main PROC LDR r0,|L0.28| LDR r1,[r0,#0] ; 将变量a载入寄存器r1 STR r1,[r0,#4] ; 将寄存器r1存储至变量b LDR r1,[r0,#4] ; 将变量b载入寄存器r1 STR r1,[r0,#8] ; 将寄存器r1存储至变量c MOV r0,#0 BX lr ENDP
对此,我们可以清楚的发现:在未使用volatile的汇编代码中,当变量b赋值给变量c时,直接将寄存器r1更新至变量c;而在使用volatile修饰变量b以后,同样变量b赋值给变量c时,先把变量b的值更新至寄存器r1(强制更新,红底色代码),再把寄存器r1更新至变量c。使用volatile修饰的变量会被编译器标记为易改变的变量,编译器在处理该变量赋值操作时会添加同步该变量(内存)至寄存器的操作。