慎用volatile修饰全局变量
【说明】 全局变量尽量不使用volatile修饰 ,以便编译器优化汇编代码提高性能。
【原理】 volatile关键字用以提醒编译器,其修饰的变量随时有可能被改变,需要保证编译后的程序每次需要存储或读取这个变量的时候,都需要直接访问其变量地址。
从本质上讲,volatile关键字的目的是抑制编译器的编译期优化:如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
为了避免编译器优化时将变量先读取到寄存器,而后直接从寄存器取值,而忽略了其他线程更改此变量时的场景。解释为“直接存取原始内存地址”更合适。一般来说,通过mmap技术将外部寄存器对象映射到内存地址的场景下,外部寄存器可能被硬件修改,此时需要增加volatile关键字修饰;
【注意事项】
- 针对外部寄存器、跨线程共享变量如果误删除其volatile关键字可能导致编译器误优化,从而引入功能问题;
- 采用volatile关键字修饰变量,不是一种线程间同步的技术,无法做到严格的同步保证(尤其是对于弱一致性的缓存模型);因此对于多线程并发同步、或处理器乱序带来等场景,需要针对性地选择线程同步、内存屏障等技术手段语语义解决;
【案例】(需要使用volatile关键字修饰的场景)
int Func()
{
char *reg = (char *)0x1234;
while(*reg != 0){
}
return 0;
}
对应的汇编代码为:
Func():
mov r3, #4608
ldrb r0, [r3, #52] @ zero_extendqisi2
.L2:
cmp r0, #0
bxeq lr
cmp r0, #0
bne .L2
bx lr
更换成volatile关键字修饰之后,
int Func()
{
char volatile *reg = (char *)0x1234;
while(*reg != 0){
}
return 0;
}
对应的汇编代码为:
Func():
mov r3, #4608
.L2:
ldrb r0, [r3, #52] @ zero_extendqisi2
ands r0, r0, #255
bne .L2
bx lr
上述案例能够看到,增加volatile修饰,可以避免编译器对变量的读取过程进行优化,从而导致功能异常的问题;除了上述需要读取外部寄存器等场景外,不建议volatile关键字修饰。
父主题: 变量