C++系列总结——volatile关键字
前言
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 <main+16>
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 <main+8>
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 <printf@plt>
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 <printf@plt>
0x00000000004005b2 <+34>: xor %eax,%eax
0x00000000004005b4 <+36>: add $0x8,%rsp
0x00000000004005b8 <+40>: retq
结语
volatile
保证了单一执行线程内,对其修饰的变量的访问不能被优化,以及对另一先序或后序该volatile
变量的volatile
变量的访问不会被重排序。
从上面的例子能看看出,嵌入式开发中,volatile
会比较常用,但在普通应用开发中其实很少用到,一般配合信号处理函数使用。
volatile
并不能保证多线程安全。
上述代码运行必崩溃,看看意思就好。
gcc version 4.8.5 20150623
C++系列总结——volatile关键字的更多相关文章
- 【面试普通人VS高手系列】volatile关键字有什么用?它的实现原理是什么?
一个工作了6年的Java程序员,在阿里二面,被问到"volatile"关键字. 然后,就没有然后了- 同样,另外一个去美团面试的工作4年的小伙伴,也被"volatile关 ...
- Java多线程干货系列—(四)volatile关键字
原文地址:http://tengj.top/2016/05/06/threadvolatile4/ <h1 id="前言"><a href="#前言&q ...
- 并发系列2:Java并发的基石,volatile关键字、synchronized关键字、乐观锁CAS操作
由并发大师Doug Lea操刀的并发包Concurrent是并发编程的重要包,而并发包的基石又是volatile关键字.synchronized关键字.乐观锁CAS操作这些基础.因此了解他们的原理对我 ...
- 【JUC系列第一篇】-Volatile关键字及内存可见性
作者:毕来生 微信:878799579 什么是JUC? JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类 2.Volatile关键字 1.如果一个变量被volat ...
- java并发系列(六)-----Java并发:volatile关键字解析
在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...
- Java单例模式和volatile关键字
单例模式是最简单的设计模式,实现也非常"简单".一直以为我写没有问题,直到被 Coverity 打脸. 1. 暴露问题 前段时间,有段代码被 Coverity 警告了,简化一下代码 ...
- 就是要你懂Java中volatile关键字实现原理
原文地址http://www.cnblogs.com/xrq730/p/7048693.html,转载请注明出处,谢谢 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是j ...
- Java并发——volatile关键字
什么是内存可见性? 这里就要提一下JMM(Java内存模型).当线程在运行的时候,并不是直接直接修改电脑主内存中的变量的值.线程间通讯也不是直接把一个线程的变量的值传给另一个线程,让其刷新变量.下面是 ...
- Java的 volatile关键字的底层实现原理
我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用.本文详细解读一下volat ...
随机推荐
- 【RL-TCPnet网络教程】第36章 RL-TCPnet之FTP服务器
第36章 RL-TCPnet之FTP服务器 本章节为大家讲解RL-TCPnet的FTP服务器应用,学习本章节前,务必要优先学习第35章的FTP基础知识.有了这些基础知识之后,再搞本章节会有事 ...
- Linux 虚拟网络设备详解之 Bridge 网桥
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 前面几篇文章介 ...
- so库链接和运行时选择哪个路径下的库?
总结今天遇到的一个so库链接.运行问题. 这几天修改了xapian的源码,重新编译so库,再重新编译之前的demo程序,跑起来后却发现执行的函数并非我修改过的,使用的还是老版本.折腾了一会儿,发现是因 ...
- python—day9 函数的定义、操作使用方法、函数的分类、函数的嵌套调用
一.函数的定义 函数的四个组成部分: 函数名. 函数体. 函数返回值. 函数参数 1.概念:重复利用的工具,可以完成特定功能的代码块,函数是存放代码块的容器 2.定义: def:声明函数的关键词 函数 ...
- linux 关机命令shutdown
linux系统,正确的关机很重要,因为linux是多任.多用户系统,在后台可能同时有很多人在主机上面工作.不正确的挂机可能会导致数据中断. 1.关机前的操作(可以不进行) 可以使用who命令查看系统有 ...
- Python内置函数(25)——getattr
英文文档: getattr(object, name[, default]) Return the value of the named attribute of object. name must ...
- How does the vuejs add the query and walk the object?
让这个老实返回的页面添加特殊路由,这个页面常常都是登录注册.这次我们根据登录举例. 省略 { path:'/login?url=:url', name:'loginfirst', component: ...
- FloatingActionButton(悬浮按钮)使用学习<一>
FloatingActionButton简称FAB. 一. 对于App或某个页面中是否要使用FloatingActionButton必要性: FAB代表一个App或一个页面中最主要的操 ...
- 消息队列、socket(UDP)实现简易聊天系统
前言: 最近在学进程间通信,所以做了一个小项目练习一下.主要用消息队列和socket(UDP)实现这个系统,并数据库存储数据,对C语言操作数据库不熟悉的可以参照我的这篇博客:https://www.c ...
- 使用mpvue开发小程序教程(一)
前段时间,美团开源了mpvue这个项目,使得我们又多了一种用来开发小程序的框架选项.由于mpvue框架是完全基于Vue框架的(重写了其runtime和compiler),因此在用法上面是高度和Vue一 ...