【ARM 嵌入式 C 入门及渐进 6 -- Linux GCC volatile 关键字原理及使用】

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 的值是由某个硬件中断服务程序中修改的。如当某个按键按下时,就会对触发中断产生,然后执行对应的中断服务程序,在中断程序中修改 flagtrue,这样上面的程序就能够得以继续运行。

但是,编译器并不知道 flag 的值会被别的程序修改,在它进行优化的时候,可能会把 flag 的值先读入CPU 的某个通用寄存器中,然后等待那个寄存器变为 true。如果不幸进行了这样的优化,那么 while 循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正 flag 变量的值,就需要定义为如下形式:

volatile int flag;

需要注意的是,没有 volatile可能能正常运行,但是可能修改了编译器的优化等级 之后就又不能正常运行了。因此经常会出现 debug 版本正常,但是 release 版本却不能正常的问题。所以为了安全起见,如果遇到需要等待别的程序修改某个变量的话,就加上 volatile 关键字

1.1.2 volatile 使用场景小结

volatile 主要用于变量会异步改变的情况下, 主要有三个方面:

  1. 外设寄存器,如状态寄存器;
  2. 中断和主循环都会用到的全局变量;
  3. 操作系统中的线程间都会用到的公共变量。

推荐阅读
https://blog.csdn.net/dlutbrucezhang/article/details/8820550

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主公CodingCos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值