C++语法|volatile关键字!从CPU角度进行理解

个人认为C++有着复杂、臃肿的语法系统,但是也正是因为这些特性,让我们在使用C++时既能深入到操作系统级的控制,也能抽象出来完全专注于一些业务问题。
这里为大家推荐一本书和汇编代码阅读网站!
《CPU眼里的C/C++》
Compiler Explorer
我们一起来抽丝剥茧,语法上的一切弯弯绕绕,在CPU汇编指令的面前众生平等。

文章目录

  • 简述volatile概念
  • 案例——不加valatile的情况
    • 案例代码
  • 案例——加上valatile关键字
    • volatile的真正用意
  • volatile的典型应用场景
    • 1.多线程
      • 不加volatile关键字
      • 加上volatile关键字
    • 2.驱动开发
      • 不加volatile关键字
      • 加上volatile关键字
  • 总结

简述volatile概念

volatile可以放到变量的前面,告诉编译器这个变量是易变的、不稳定的。
只能说是一脸懵逼。

案例——不加valatile的情况

本节首先会给一个案例代码,其中包括C++源代码和对应的汇编指令。不懂汇编不要紧,我会进行详细的阐述说明。

首先我们一起探究一下对于一个常规变量,编译器是如何进行处理的。

案例代码

在这里插入图片描述

我们先从CPU的角度理解一下常规变量,然后做一下函数调用:左边CPP源代码的背景色和右边汇编的背景色是一一对应的。
我们对该案例进行以下分析:

  1. while循环体对应的的汇编指令
mov		eax, DWORO PTR a[rip]
cmp		eax, 1
jg		.L2

指令一:读取变量a的值,将a的值(a是全局变量)读取到eax寄存器中。
指令二:比较a和1的大小
指令三:如果a大于1,则跳回,把前面两条指令再做一次。

  1. 提高编译器优化级别
    我们将编译器的优化级别设置为-O2,这通常针对编译过的程序进行性能优化的编译器选项,其目标是提升程序的执行速度,同时尽量保持编译时间和结果程序的大小在合理范围内。
    在这里插入图片描述
    我们整体的代码只上下五条汇编指令!因为编译器会把变量a当作常量来对待。既然a被认为是常量,那就说明a与1比较的结果对于编译器来说是预先可知的,所以我们的汇编竟然直接不执行while循环

这就是编译器在背后为我们做的事情!

案例——加上valatile关键字

如果我们给a加上volatile
其他部分代码都不变,我们在a前面加上volatile关键字,汇编指令如下:
在这里插入图片描述

如上,尽管我们进行了2级优化,但是对于while对应的3条指令编译器并不会把它优化掉了!而是老老实实得从内存中取指令到寄存器,然后寄存器比较寄存器和1的大小,最后跳回或跳出循环。

volatile的真正用意

前文所说:“易变的、不稳定的”的描述其实都是说给编译器听的。

因为编译器会把他认为值不会改变的变量当作常量对待,以此缩减不必要的CPU指令,换取大幅度的效率优化。而volatile就是阻止这种优化,让CPU老老实实从内存中读、写变量

由于编译器的技术进步和各大编译器之间的巨大差异,判定一个变量是否可以被优化,也没有一个统一的标准。这也就是volatile存在的意义!

volatile的典型应用场景

1.多线程

不加volatile关键字

我们在另外一个文件file_B或者另外一个线程task_B中,改变了a的值,让其满足while循环的条件。

//file_A.cpp
int a = 0;
int task_A() {while(a > 1) {// do something}return 1;
}//file_B.cpp
extern int a;
void task_B() {a = 2;
}

在这里插入图片描述
我们明明已经在文件B或者线程B的task_B函数更改了,a的值,但是由于编译器将while循环的指令进行了优化,while直接不满足条件而跳出循环!

加上volatile关键字

在这里插入图片描述
加上volatile关键字后,编译器不进行优化,CPU老老实实按照我们的想法来干活。

2.驱动开发

不加volatile关键字

在做驱动开发的时候,我需要通过读一个寄存器来了解USB设备的插拔状态:

unsigned int *REGISTER = (unsigned int *)0xFF001100;int detect () {//initialize register*REGISTER = 0xff;//read register for USB statusunsigned int status = *REGISTER;return status;
}

在这里插入图片描述
这里经过编译器的代码优化后,尽管将REGISTER寄存器的值乖乖读到rax中,但是我们的unsigned int status = *REGISTER竟然被直接优化了!
因为在编译器眼里,我们就是在读一个毫无变化的值,所以他干脆不读rax寄存器,而是直接把立即数255(0xff)返回。这样我们得到的USB状态永远都是毫无意义的255。

加上volatile关键字

在这里插入图片描述

加上之后,我们的编译器终于开始认认真真的读rax中的寄存器数字了。很舒服

总结

  1. 编译器的代码优化能力:编译器可能对代码中的变量读、写进行适当的优化,避免没有必要的内存读写操作,这往往会大幅度提升程序的执行效率。但当程序变得复杂时,就算编译器也不能完全领会程序员意图,所以这种优化有时候是有害的,需要volatile站出来解决。
  2. volatile关键字到底有什么用:volatile关键字,就是用来避免编译器的优化操作,用来保证每次对变量的读、写都是对内存的真实操作;特别是不会让编译器把某些变量当作常量对待。
  3. 如何判断开不开优化:在编译器不开优化的情况下,很多时候,是否加volatile不会有什么差异,这也让volatile的使用场景变得模糊。判定volatile是否有存在的必要,往往需要查看代码对应的CPU(汇编)指令,看看它是否符合程序员预期。

《CPU眼里的C/C++》的作者阿布大哥在本节最后讲了他的volatile应用场景:
“网卡会把每一个以太网数据包发送两遍。虽然依靠强大的TCP/IP协议的应对能力,这并不会影响网络通信和软件功能。
最后经过排查网卡驱动程序,之所以发送两次,是因为程序判定每次发送以太网包都是不成功的!所以会尝试在发一次。明明成功地发送了,为何被判定为不成功呢?
原来用来标识成功与否的寄存器,他的值被保存在一个变量里面,但由于优化的原因,而该变量被编译器当作常量0来对待了!”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/696095.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode_栈和队列相关OJ题目

✨✨所属专栏:LeetCode刷题专栏✨✨ ✨✨作者主页:嶔某✨✨ 上一篇:数据结构_栈和队列(Stack & Queue)-CSDN博客 有效的括号 解析: 这里我们用数组实现的栈来解决这个问题,在有了栈的几个基础接口之后,我们运用这…

vue3.0(七) 计算属性(computed)

文章目录 1 计算属性(computed )1.1 computed使用1.2 computed使用场景1.4 computed的注意点1.4 computed的原理1.5 computed的示例 computed 和 Methods 的区别 1 计算属性(computed ) 在 Vue 3 中,computed 是一个用…

linux笔记5--shell命令2

文章目录 一. linux中的任务管理1. 图形界面2. 命令① top命令② grep命令③ ps命令补充: ④ kill命令图形界面杀死进程 二. 挂载(硬盘方面最重要的一个知识点)1. 什么是挂载2. 关于挂载目录① Windows② linux查看硬件分区情况(/dev下):更改挂载目录结束…

视频剪辑的技巧:掌握如何高效批量调整视频尺寸的方法

在视频剪辑的过程中,调整视频尺寸是一个常见的需求。无论是为了适应不同平台的播放要求,还是为了统一多个视频的尺寸以提升观看体验,掌握高效批量调整视频尺寸的技巧都显得尤为重要。本文将为您详细介绍云炫AI智剪如何高效地进行这一操作&…

【计算机网络】数据链路层 组帧 习题4

组帧 发送方根据一定的规则将网络层递交的分组封装成帧(也称为组帧)。 组帧时,既要加首部,也要加尾部,原因是,在网络信息中,帧是以最小单位传输的。所以接收方要正确地接收帧,就必须清楚该帧在一串比特串中…

Find My腰包|苹果Find My技术与腰包结合,智能防丢,全球定位

腰包具有显瘦和显高的双重功效,它不仅能提高腰线、拉长腿部线条,还能遮住腹部多余的赘肉,从而在视觉上达到变高的效果,使整体看起来更加显瘦。除了时尚功能,腰包在运动中也有其独特的用途。例如,在跑步时&a…

tab 滑动小案例

效果&#xff1a; 代码&#xff1a; <template><view class"content"><view class"tab"><view v-for"(item,index) in dataList" :key"index" class"tab_item" click"slideTab(index)">…

48.乐理基础-音符的组合方式-休止符

休止符 音乐中总有一些停顿的地方&#xff0c;一次停顿多久是创作人固定好的&#xff0c;休止符就是用来表示每一次停顿多久 需要停顿的位置就用 0 来表示&#xff0c;数字 0 就是简谱中的休止符 音符有全音符、二分音符、四分音符、八分音符、十六分音符、三十二分音符等&…

Spring Boot项目怎么集成Gitee登录

一、背景 现在的越来越多的项目&#xff0c;需要集成第三方系统进行登录。今天我们以Spring Boot项目集成Gitee为例&#xff0c;演示一下怎么使用Oauth2协议&#xff0c;集成第三方系统登录。 不了解oauth2的&#xff0c;可以看我之前的文章。Ouath2是怎么实现在第三方应用认…

PyQt6--Python桌面开发(12.QpushButton按钮控件)

一.按钮类控件 二.QpushButton按钮控件 2.1QAbstractButton类属性 2.2QpushButton类属性

Handler详解

文章目录 主线程和子线程消息机制作用原理内存泄露HandlerThread含义 线程池参考链接 主线程和子线程 从用途上来说Android的线程主要分为主线程和子线程两类&#xff0c;主线程主要处理和界面相关的工作&#xff0c;子线程主要处理耗时操作。除Thread之外&#xff0c;Android…

背完这些软件测试核心面试题,offer轻松拿捏了!

你赞同过 软件测试和开发 相关内容 01、您所熟悉的测试用例设计方法都有哪些&#xff1f;请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。 答&#xff1a;有黑盒和白盒两种测试种类&#xff0c;黑盒有等价类划分法&#xff0c;边界分析法&#xff0c;因果图法和…