编译原理:volatile关键字

2020-02-19  本文已影响0人  Minority

1、volatile有什么含义?有什么用法?

官方定义是:

一个变量也许会被后台程序改变。
关键字volatile与const绝对独立。它指示一个变量也许会被某种方式修改,这种方式按照正常程序流程分析是无法预知的(例如,一个变量也许会被一个中断服务程序所修改)。这个关键字使用以下语法定义:

volatile data-defiinition;

注:变量如果加了voletile修饰,则会从内存中重新装载内容,而不是直接从寄存器中拷贝内容。

volatile的作用是告知编译器,它修饰的变量随时都可能被改变,因此,编译后的程序每次在使用该变量的值时,都会从变量的内存地址中读取数据,而不是从寄存器中获取。

下面是volatile变量的几个例子:
  1. 并行设备的硬件寄存器(如:状态寄存器)
  2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
  3. 多线程应用中被几个任务共享的变量

2、volatile和编译器的优化有关:

在本次线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时有时会先把变量读取到一个寄存器中;以后,再读取变量值时,就直接从寄存器中读取;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以保持一致。 在本次线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时有时会先把变量读取到一个寄存器中;以后,再读取变量值时,就直接从寄存器中读取;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以保持一致。

当变量因别的线程值发生改变,上面寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。

当寄存器因别的线程改变了值,原变量的值也不会改变,也会造成应用程序读取的值和实际的变量值不一致。

小例子:

#include <stdio.h> 
#include <iostream>

int main(void)
{
    const volatile int local = 10; // or const int local = 10;
    int* ptr = (int*)&local;

    printf("Initial value of local : %d \n", local);

    *ptr = 100;

    printf("Modified value of local: %d \n", local);

    std::cin.get();

}

没有使用volatile时,通过g++ 编译后的结果:

00t00000000000400596 <main>:
  400596:   55                      push   %rbp
  400597:   48 89 e5                mov    %rsp,%rbp
  40059a:   48 83 ec 20             sub    $0x20,%rsp
  40059e:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  4005a5:   00 00 
  4005a7:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  4005ab:   31 c0                   xor    %eax,%eax
  //-0x14(%rbp)是local所在位置(也即,local=10)    
  4005ad:   c7 45 ec 0a 00 00 00    movl   $0xa,-0x14(%rbp)
  //取local地址到寄存器                                                 
  4005b4:   48 8d 45 ec             lea    -0x14(%rbp),%rax  
 //将local的地址赋给ptr(-0x10(%rbp))
  4005b8:   48 89 45 f0             mov    %rax,-0x10(%rbp)   
 //将10写入寄存器%esi,也即printf的第二个参数   
  4005bc:   be 0a 00 00 00          mov    $0xa,%esi    
  //%edi是printf的一个参数,也即字符串"Initial value...."                                               
  4005c1:   bf 94 06 40 00          mov    $0x400694,%edi 
                                               
  4005c6:   b8 00 00 00 00          mov    $0x0,%eax
  4005cb:   e8 a0 fe ff ff          callq  400470 <printf@plt>
 //取ptr的值,也即local的地址
  4005d0:   48 8b 45 f0             mov    -0x10(%rbp),%rax   
 //将100写入ptr所指向的地址,也即local 
  4005d4:   c7 00 64 00 00 00       movl   $0x64,(%rax)   
  //将10写入%esi,也即printf
  4005da:   be 0a 00 00 00          mov    $0xa,%esi     
  4005df:   bf b2 06 40 00          mov    $0x4006b2,%edi
  4005e4:   b8 00 00 00 00          mov    $0x0,%eax
  4005e9:   e8 82 fe ff ff          callq  400470 <printf@plt>
  4005ee:   b8 00 00 00 00          mov    $0x0,%eax
  4005f3:   48 8b 55 f8             mov    -0x8(%rbp),%rdx
  4005f7:   64 48 33 14 25 28 00    xor    %fs:0x28,%rdx
  4005fe:   00 00 
  400600:   74 05                   je     400607 <main+0x71>
  400602:   e8 59 fe ff ff          callq  400460 <__stack_chk_fail@plt>
  400607:   c9                      leaveq 
  400608:   c3                      retq   
  400609:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)

可以看到在没有volatile的时候,local就直接被优化成了0xa;在第二次调用printf的时候,作为参数传给printf的时候,也是直接 mov $0xa, %esi;所以修改并没有体现。

使用volatile的汇编,重点要看的就是第二个printf之前的部分代码,在准备printf的参数,这里可以看到,多了一步从内存中取值的过程:

0000000000400596 <main>:
  400596:   55                      push   %rbp
  400597:   48 89 e5                mov    %rsp,%rbp
  40059a:   48 83 ec 20             sub    $0x20,%rsp
  40059e:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  4005a5:   00 00 
  4005a7:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  4005ab:   31 c0                   xor    %eax,%eax
  4005ad:   c7 45 ec 0a 00 00 00    movl   $0xa,-0x14(%rbp)
  4005b4:   48 8d 45 ec             lea    -0x14(%rbp),%rax
  4005b8:   48 89 45 f0             mov    %rax,-0x10(%rbp) 
  4005bc:   8b 45 ec                mov    -0x14(%rbp),%eax 
  4005bf:   89 c6                   mov    %eax,%esi
  4005c1:   bf 94 06 40 00          mov    $0x400694,%edi  
  4005c6:   b8 00 00 00 00          mov    $0x0,%eax
  4005cb:   e8 a0 fe ff ff          callq  400470 <printf@plt>
  4005d0:   48 8b 45 f0             mov    -0x10(%rbp),%rax
 //依然是更新内存
  4005d4:   c7 00 64 00 00 00       movl   $0x64,(%rax)  
 //取local的值(更新之后的,也即100)
  4005da:   8b 45 ec                mov    -0x14(%rbp),%eax  
 // 将100放入%esi   
  4005dd:   89 c6                   mov    %eax,%esi       
  4005df:   bf b2 06 40 00          mov    $0x4006b2,%edi
  4005e4:   b8 00 00 00 00          mov    $0x0,%eax
  4005e9:   e8 82 fe ff ff          callq  400470 <printf@plt>
  4005ee:   b8 00 00 00 00          mov    $0x0,%eax
  4005f3:   48 8b 55 f8             mov    -0x8(%rbp),%rdx
  4005f7:   64 48 33 14 25 28 00    xor    %fs:0x28,%rdx

即有volatile的时候,会执行一次:

mov -0x14(%rbp),%eax
mov %eax,%esi

从内存中重新装载内容,而不是直接从寄存器中拷贝内容。

上一篇下一篇

猜你喜欢

热点阅读