博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++系列总结——volatile关键字
阅读量:5949 次
发布时间:2019-06-19

本文共 3871 字,大约阅读时间需要 12 分钟。

前言

volatile的中文意思是易变的,但这个易变和mutable是不同的含义。mutable是指编译期的易变,根据语法编译器默认不会让我们修改某些变量,但是加上mutable让编译器知道我们要修改的态度很强硬。而volatile的易变是指运行期的易变,这些变化是编译器无法感知的变化,让编译器不要瞎优化。

volatile如何影响编译结果

例一

编译器会将其认为无用的死代码优化掉。

int main(){    int* reg = 0x123456; // 假设0x123456是某个特殊寄存器的地址    *reg = 0x1;    *reg = 0x2;    *reg = 0x3;    *reg = 0x4;    return 0;}

上面的代码通过g++ a.cpp -O2编译后,你会发现只有*reg = 0x4;生效了,其他语句被优化掉了。

0x0000000000400540 <+0>: movl   $0x4,0x123456   0x000000000040054b <+11>:    xor    %eax,%eax   0x000000000040054d <+13>:    retq

一般情况下,这没有什么影响,但是上例中的代码实际是初始化某个设备的状态,任何状态的赋值都不能被省略,否则可能导致设备异常无法工作,此时就需要用到volatile。当加上volatile修饰后,同样通过g++ a.cpp -O2编译后,没有任何语句被优化掉

0x0000000000400540 <+0>: movl   $0x1,0x123456   0x000000000040054b <+11>:    xor    %eax,%eax   0x000000000040054d <+13>:    movl   $0x2,0x123456   0x0000000000400558 <+24>:    movl   $0x3,0x123456   0x0000000000400563 <+35>:    movl   $0x4,0x123456   0x000000000040056e <+46>:    retq

例二

因为寄存器速度快,所以编译器会使用寄存器达到优化的目的,避免频繁操作内存。

玩过单片机或者ARM板的同学肯定都写过跑马灯程序,下面的程序就算简单模拟一下LED灯忽闪忽闪。

int main(){    int* a = (int*)0x123456;     for( int i = 0; i < 1000; ++i ){        *a = ~(*a); // 反复取反,开关LED灯        sleep( 1 );    }    return 0;}

通过g++ a.cpp -O2编译后,你会发现每次取反后的值都保存在寄存器edx中,只在最后把edx里的值保存到了0x123456,这相当于只开关了LED灯一次,不符合预期。

0x0000000000400540 <+0>: mov    0x123456,%edx   0x0000000000400547 <+7>: mov    $0x3e8,%eax   0x000000000040054c <+12>:    nopl   0x0(%rax)   0x0000000000400550 <+16>:    sub    $0x1,%eax   0x0000000000400553 <+19>:    not    %edx   0x0000000000400555 <+21>:    jne    0x400550 
0x0000000000400557 <+23>: mov %edx,0x123456 0x000000000040055e <+30>: xor %eax,%eax 0x0000000000400560 <+32>: retq

当加上volatile后,每次取反结果都会存入0x123456,开关LED灯1000次符合预期。

=> 0x0000000000400540 <+0>: mov    $0x3e8,%edx   0x0000000000400545 <+5>: nopl   (%rax)   0x0000000000400548 <+8>: mov    0x123456,%eax   0x000000000040054f <+15>:    sub    $0x1,%edx   0x0000000000400552 <+18>:    not    %eax   0x0000000000400554 <+20>:    mov    %eax,0x123456      # 每次取反结果都会存入0x123456   0x000000000040055b <+27>:    jne    0x400548 
0x000000000040055d <+29>: xor %eax,%eax 0x000000000040055f <+31>: retq

上面这个例子还可以反一下,即程序统计外部开关LED灯的开关次数(通过中断感知),如果使用寄存器优化的话,CPU可能只感知到一次变化。

例三

编译器可以重排指令方便指令流水化处理,达到优化目的。

int main(){    int* a = (int*)0x123456;  // 0x123456是某个设备地址    *a = 0x1; // 开启该设备    int* b = (int*)0x654321; // 0x654321是该设备的数据读取地址    printf( "%d\n", *b ); // 读取该地址的数据    return 0;;}

上面代码的逻辑是开启某个设备后,从该设备指定地址读取数据,存在因果关系,但编译器无法识别。因此通过g++ a.cpp -O2编译后,你会发现*b被提前了,程序不符合预期。

=> 0x0000000000400590 <+0>: sub    $0x8,%rsp   0x0000000000400594 <+4>: mov    0x654321,%esi # 先从0x654321读取数据放入esi作为printf的第二个参数,也就是先执行了*b   0x000000000040059b <+11>:    movl   $0x1,0x123456 # 后将0x1设置到0x123456   0x00000000004005a6 <+22>:    mov    $0x400760,%edi # "%d\n"放入edi作为printf的第一个参数   0x00000000004005ab <+27>:    xor    %eax,%eax   0x00000000004005ad <+29>:    callq  0x400530 
0x00000000004005b2 <+34>: xor %eax,%eax 0x00000000004005b4 <+36>: add $0x8,%rsp 0x00000000004005b8 <+40>: retq

使用volatile修饰a和b后,指令执行顺序符合预期

=> 0x0000000000400590 <+0>: sub    $0x8,%rsp   0x0000000000400594 <+4>: movl   $0x1,0x123456 # 先将0x1设置到0x123456   0x000000000040059f <+15>:    mov    0x654321,%esi # 后从0x654321读取数据放入esi作为printf的第二个参数,也就是先执行了*b   0x00000000004005a6 <+22>:    mov    $0x400760,%edi   0x00000000004005ab <+27>:    xor    %eax,%eax   0x00000000004005ad <+29>:    callq  0x400530 
0x00000000004005b2 <+34>: xor %eax,%eax 0x00000000004005b4 <+36>: add $0x8,%rsp 0x00000000004005b8 <+40>: retq

结语

volatile保证了单一执行线程内,对其修饰的变量的访问不能被优化,以及对另一先序或后序该volatile变量的volatile变量的访问不会被重排序。

从上面的例子能看看出,嵌入式开发中,volatile会比较常用,但在普通应用开发中其实很少用到,一般配合信号处理函数使用。

volatile并不能保证多线程安全。

上述代码运行必崩溃,看看意思就好。

gcc version 4.8.5 20150623

转载于:https://www.cnblogs.com/yizui/p/10628020.html

你可能感兴趣的文章
linux下MySQL 5.6源码安装
查看>>
2018,从梦想到事业
查看>>
python中的字典用法大全的代码
查看>>
如何挑选优质光模块?
查看>>
初学telnet
查看>>
C++线程入口函数的几种方式
查看>>
成都课得在线|UI该不该放入网络运营范畴
查看>>
内联元素的padding,margin,border等不起作用的原因
查看>>
事务与并发控制
查看>>
初识shell文本处理工具之gawk-sed
查看>>
也来谈谈RPC
查看>>
Cisco ASA SSL ×××远程访问设置 二
查看>>
构建镜像 - 每天5分钟玩转容器技术(12)
查看>>
平衡二叉树
查看>>
centos7 中 systemd systemctl管理服务的命令
查看>>
企业级办公室iptables防火墙应用案例
查看>>
Ubuntu python 安装使用sqlalchemy
查看>>
HAProxy 之 ACL介绍和使用
查看>>
OEL 6.4中安装Oracle 11g_R2_64bit
查看>>
vSphere 5 中的多网卡 vMotion
查看>>