既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
本文主要介绍 C/C++ 编程语言中 volatile 关键字的相关知识。
1 概述
1.1 Why volatile
volatile 关键字,最早出现于 19 世纪 70 年代,被用于处理 MMIO(Memory-mapped I/O) 带来的问题。
在引入 MMIO 之后,一块内存地址既有可能是真正的内存,也有可能是映射的一个 I/O 端口。因此,读/写一个内存地址,既有可能是真正地操作内存,也有可能是读/写一个 I/O 设备。
那么 MMIO 为什么需要引入 volatile 关键字呢?我们结合下面这段示例代码进行解释:
unsigned int *p = FunB();
unsigned int a;
unsigned int b;
a = *p; // 语句1
b = *p; // 语句2
*p = a; // 语句3
*p = b; // 语句4
在上述代码片段中,指针 p 既有可能指向一个内存地址,也有可能指向一个 I/O 设备。如果指针 p 指向的是 I/O 设备,那么语句 1 和语句 2 中的变量 a 和变量 b,就会接收到 I/O 设备的连续两个字节。但是,指针 p 也有可能指向内存地址,这种情况下,编译器就会进行语句优化,编译器的优化策略会判断变量 a 和变量 b 同时从同一个内存地址读取数据,因此在执行完语句 1 之后,直接将变量 a 赋值给变量 b。对于指针 p 指向 I/O 设备的这种情况,就需要防止编译器进行此优化,即不能假设指针 b 指向的内容不变(对应 volatile 关键字的易变性特性)。
同样,语句 3 和语句 4 也有类似的问题,编译器发现将变量 a 和 b 同时赋值给指针 p 是无意义的,因此可能会优化语句 3 中的赋值操作,而仅仅保留语句 4。对于指针 p 指向I/O设备的情况,也需要防止编译器将类似的写操作给优化消失了(对应 volatile 关键字的不可优化特性)。
对于 I/O 设备,编译器不能随意交互指令的顺序,因为指令顺序一变,写入 I/O 设备的内容也就发生变化了(对应 volatile 关键字的顺序性特性)。
为了满足 MMIO 的这三点需求,就有了 volatile 关键字。
1.2 IN C/C++
在 C/C++ 编程语言中,使用 volatile 关键字声明的变量(或对象)通常具有与优化、多线程相关的特殊属性。通常,volatile 关键字用来阻止(伪)编译器对其认为的、无法“被代码本身”改变的代码(变量或对象)进行优化。如在 C/C++ 编程语言中,volatile 关键字可以用来提醒编译器使用 volatile 声明的变量随时有可能改变,因此编译器在代码编译时就不会对该变量进行某些激进的优化,故而编译生成的程序在每次存储或读取该变量时,都会直接从内存地址中读取数据。相反,如果该变量没有使用 volatile 关键字进行声明,则编译器可能会优化读取和存储操作,可能暂时使用寄存器中该变量的值,而如果这个变量由别的程序(线程)更新了的话,就会出现(内存中与寄存器中的)变量值不一致的现象。
在 C/C++ 编程语言中,使用 volatile 关键字声明的变量具有三种特性:易变的、不可优化的、顺序执行的。下面分别对这三种特性进行介绍。
2 易变的
volatile 在词典中的主要释义就是“易变的”。
在 C/C++ 编程语言中,volatile 的易变性体现在:假设有读、写两条语句,依次对同一个 volatile 变量进行操作,那么后一条的读操作不会直接使用前一条的写操作对应的 volatile 变量的寄存器内容,而是重新从内存中读取该 volatile 变量的值。
上述描述的(部分)示例代码内容如下:
volatile int nNum = 0; // 将nNum声明为volatile
int nSum = 0;
nNum = FunA(); // nNum被写入的新内容,其值会缓存在寄存器中
nSum = nNum + 1; // 此处会从内存(而非寄存器)中读取nNum的值
3 不可优化的
在 C/C++ 编程语言中,volatile 的第二个特性是“不可优化性”。volatile 会告诉编译器,不要对 volatile 声明的变量进行各种激进的优化(甚至将变量直接消除),从而保证程序员写在代码中的指令一定会被执行。
上述描述的(部分)示例代码内容如下:
volatile int nNum; // 将nNum声明为volatile
nNum = 1;
printf("nNum is: %d", nNum);
在上述代码中,如果变量 nNum 没有声明为 volatile 类型,则编译器在编译过程中就会对其进行优化,直接使用常量“1”进行替换(这样优化之后,生成的汇编代码很简介,执行时效率很高)。而当使用 volatile 进行声明后,编译器则不会对其进行优化,nNum 变量仍旧存在,编译器会将该变量从内存中取出,放入寄存器之中,然后再调用 printf() 函数进行打印。
4 顺序执行的
在 C/C++ 编程语言中,volatile 的第三个特性是“顺序执行特性”,即能够保证 volatile 变量间的顺序性不会被编译器进行乱序优化。
C/C++ 编译器最基本优化原理:保证一段程序的输出,在优化前后无变化。
为了对本特性进行深入了解,下面以两个变量(nNum1 和 nNum2)为例(既然存在“顺序执行”,那描述对象必然大于一个),介绍 volatile 的顺序执行特性,示例代码内容如下:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
et/topics/618668825)**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!