美文网首页
编译原理: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

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

相关文章

  • java多线程与高并发(三)volatile与CAS

    1.volatile关键字原理 用 volatile 关键字修饰的共享变量,编译成字节码后增加 Lock 前缀指令...

  • Volatile关键字实现原理

    Volatile关键字实现原理 在这一篇文章中,我将介绍java中实现volatile关键字相关的知识,包括编译屏...

  • Java 多线程原理

    ReentrantLock 实现原理 synchronized 关键字原理 你应该知道的 volatile 关键字...

  • 编译原理:volatile关键字

    1、volatile有什么含义?有什么用法? 官方定义是: 一个变量也许会被后台程序改变。关键字volatile与...

  • volatile与synchronized的区别

    一、volatile volatile的原理在java中,被volatile声明的关键字,jvm会在翻译的时候在c...

  • Java Volatile transient 关键字

    Java Volatile transient 关键字 java关键字volatile Volatile修饰的成员...

  • volatile关键字的作用、原理

    volatile关键字的作用、原理 在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题...

  • 关键字volatile

    一. volatile是什么? volatile关键字是一种类型修饰符,对于用它声明的类型变量,编译器对访问该变量...

  • Java面试题

    面试题: HashMap底层实现原理,红黑树,B+树,B树的结构原理,volatile关键字,CAS(比较与交换)...

  • volatile

    volatile关键字private volatile int count; volatile关键字主要有三方面作...

网友评论

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

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