美文网首页
编译原理:volatile关键字

编译原理:volatile关键字

作者: Minority | 来源:发表于2020-02-19 13:57 被阅读0次

    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

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

    相关文章

      网友评论

          本文标题:编译原理:volatile关键字

          本文链接:https://www.haomeiwen.com/subject/jdkafhtx.html