1.1 volatile 关键字
volatile
关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
当使用 volatile
声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
1.1.1 volatile 汇编代码跟踪
案例1:
void main_test(void)
{
volatile int i = 0x0;
int a = i;
int b = i;
rt_kprintf("a:%d b:%d\n", a, b);
}
void SystemInit(void)
{
main_test();
...
}
volatile
指出 i
是随时可能发生变化的,每次使用它的时候必须从 i
的地址中读取,因而编译器生成的汇编代码会重新从 i
的地址读取数据放在 a
, b
中。
37309 000172d8 <main_test>:
37310 172d8: b580 push {r7, lr}
37311 172da: b084 sub sp, #16
37312 172dc: af00 add r7, sp, #0
37313 172de: 2300 movs r3, #0 ------------>(1)
37314 172e0: 607b str r3, [r7, #4] ---------->(2)
37315 172e2: 687b ldr r3, [r7, #4] ---------->(3)
37316 172e4: 60fb str r3, [r7, #12] --------->(4)
37317 172e6: 687b ldr r3, [r7, #4] --------->(5)
37318 172e8: 60bb str r3, [r7, #8]
37319 172ea: 68ba ldr r2, [r7, #8]
37320 172ec: 68f9 ldr r1, [r7, #12]
37321 172ee: 4803 ldr r0, [pc, #12] ; (172fc <main_test+0x24>)
37322 172f0: f047 fa18 bl 5e724 <rt_kprintf>
37323 172f4: bf00 nop
37324 172f6: 3710 adds r7, #16
37325 172f8: 46bd mov sp, r7
37326 172fa: bd80 pop {r7, pc}
37327 172fc: 000adda4 andeq sp, sl, r4, lsr #27
汇编代码解释:
(1) 对 i
赋值为0x0;
(2) 将 i
的值存到栈中(内存中);
(3) 到栈中(内存中)再取出 i
的值;
(4) 将 i
的值赋值给 a
;
(5) 到栈中(内存中)再取出 i
的值;
如果去掉 volatile
修饰,将GCC 的优化等级提高到 -O2
:
void main_test(void)
{
int i = 0x0;
int a = i;
int b = i;
rt_kprintf("a:%d b:%d\n", a, b);
}
void SystemInit(void)
{
main_test();
...
}
24773 00010764 <SystemInit>:
24774 10764: 2200 movs r2, #0 ---->(1)
24775 10766: 4808 ldr r0, [pc, #32] ; (10788 <SystemInit+0x24>)
24776 10768: 4611 mov r1, r2 ----->(2)
24777 1076a: b508 push {r3, lr}
24778 1076c: f02c fc6c bl 3d048 <rt_kprintf>
去掉 volatile
修饰及提高 GCC 的编译优化等级之后,由于编译器发现两次从 i
中读数据的代码之间的代码没有对 i
进行过其它操作,在汇编中可以看到根本没有去使用 str/ldr
指令到内存中拿去数据,而是在给 i
赋值为 0
后(见(1)),后面直接使用 r2 (i)
的值给 r1(a)
赋值。
案例2:
int flag;
static void volatile_test(void)
{
do1();
while(flag==false);
do2();
}
可以看到上面的代码中的内存变量 flag
的值变为 true
, 之后才运行 do2()
,假如 变量 flag
的值是由某个硬件中断服务程序中修改的。如当某个按键按下时,就会对触发中断产生,然后执行对应的中断服务程序,在中断程序中修改 flag
为 true
,这样上面的程序就能够得以继续运行。
但是,编译器并不知道 flag
的值会被别的程序修改,在它进行优化的时候,可能会把 flag
的值先读入CPU 的某个通用寄存器中,然后等待那个寄存器变为 true
。如果不幸进行了这样的优化,那么 while
循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正 flag
变量的值,就需要定义为如下形式:
volatile int flag;
需要注意的是,没有 volatile
也可能能正常运行,但是可能修改了编译器的优化等级 之后就又不能正常运行了。因此经常会出现 debug 版本正常,但是 release 版本却不能正常的问题。所以为了安全起见,如果遇到需要等待别的程序修改某个变量的话,就加上 volatile
关键字。
1.1.2 volatile 使用场景小结
volatile
主要用于变量会异步改变的情况下, 主要有三个方面:
- 外设寄存器,如状态寄存器;
- 中断和主循环都会用到的全局变量;
- 操作系统中的线程间都会用到的公共变量。
推荐阅读:
https://blog.csdn.net/dlutbrucezhang/article/details/8820550