📌volatile 关键字作用:防止编译器过度优化,指示易变的变量可能在程序外部被修改,确保顺序执行和及时更新。
📌字面意思就是易变的、不可优化的、顺序执行的。
volatile关键字的介绍
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。
C/C++中volatile用来修饰一个变量,表示这个变量可以被编译器之外的东西改变。
- 说明
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值;
如果这个变量由别的程序更新了的话,将出现不一致的现象。
volatile 可以修饰的范围以及限制
在C++中,volatile
关键字可以修饰几乎所有的数据类型,包括基本类型(如int
,char
,float
等)、指针、类对象、结构体、联合体等。以下是一些例子:
- 基本类型:
volatile int a;
,volatile float b;
- 指针:
volatile int *p;
,int * volatile p;
,volatile int * volatile p;
(这三个例子分别表示指向volatile int
的指针,volatile
指针指向int
,volatile
指针指向volatile int
) - 类对象:如果一个类的对象被声明为
volatile
,那么只有类中被声明为volatile
的成员函数才能被调用。 - 结构体和联合体:
volatile struct S { ... };
,volatile union U { ... };
然而,volatile
关键字不能与某些其他关键字一起使用。例如,volatile
不能与constexpr
一起使用,因为constexpr
表示的是一个在编译时就能确定的常量,而volatile
表示的是一个可能在任何时候被更改的变量。这两个关键字表示的是两种完全不同的概念,因此不能一起使用。
此外,volatile
也不能与const
一起使用,因为const
表示的是一个不能被更改的变量,而volatile
表示的是一个可能在任何时候被更改的变量。这两个关键字表示的是两种完全不同的概念。
volatile关键字的使用场景
- 并行设备的硬件寄存器
存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
- 一个中断服务子程序中会访问到的非自动变量(全局变量)
中断服务程序中修改的供其它程序检测的变量需要加volatile。
- 多线程应用中被几个任务共享的变量
多任务环境下各任务间共享的标志应该加volatile
然而,需要注意的是,虽然volatile可以防止编译器进行某些类型的优化,但它并不能提供多线程环境下的同步机制。也就是说,即使一个变量被声明为volatile,在多线程环境中也不能保证对这个变量的访问是原子的。为了实现这种同步,我们需要使用诸如互斥锁(mutexes)、信号量(semaphores)、条件变量(condition variables)等同步原语。
在C++中,std::mutex
,std::condition_variable
,以及std::atomic
类型的变量都是设计用来在多线程环境中安全使用的,它们内部已经处理了所有必要的内存屏障和同步操作,所以你不需要(也不应该)将它们声明为volatile
。
volatile
关键字主要用于防止编译器优化,它告诉编译器这个变量可能会在编译器看不到的地方被修改。然而,对于std::mutex
,std::condition_variable
,以及std::atomic
类型的变量,它们的实现已经考虑到了这一点,所以你不需要使用volatile
关键字。
实际上,如果你将这些类型的变量声明为volatile
,可能会导致编译器产生错误或警告,因为这可能会让编译器认为你想要做一些不寻常的事情。
所以,总的来说,你不需要(也不应该)在多线程编程中使用volatile
关键字。你应该使用std::mutex
,std::condition_variable
,以及std::atomic
类型的变量来实现多线程同步。
volatile关键字的作用
- 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种情况.
- 不做常量合并、常量传播等优化.
- 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作,然而对Memory Mapped IO的处理是不能这样优化的。
volatile与mutable关键字的区别
- mutable只能用与类变量,不能与const同时使用;在const修饰的方法中,mutable变量数值可以发生改变;
- volatile只是运行期变量的值随时可能改变,这种改变即可能来自其他线程,也可能来自外部系统。
volatile与互斥量
- volatile关键字在多线程编程中可以用于共享变量,但它本身并不能提供线程安全的保证。volatile关键字只能确保编译器不会对该变量进行优化,从而确保每次访问都会从内存中重新读取值。然而,它并不能防止多个线程同时访问和修改共享变量时导致的数据竞争。
- 互斥量、原子操作和其他同步机制在多线程编程中更常用,因为它们可以确保线程安全。使用互斥量可以保证一次只有一个线程访问共享资源,从而避免数据竞争。原子操作可以保证某些特定的操作是不可分割的,这也有助于避免数据竞争。
在某些特定情况下,volatile关键字和互斥量、原子操作等同步机制可以一起使用。例如,当您需要在多线程环境中访问硬件寄存器时,可以使用volatile关键字和互斥量一起确保线程安全和避免编译器优化。然而,在大多数情况下,仅使用互斥量、原子操作等同步机制就足以实现线程安全。
总之,虽然volatile关键字在多线程编程中有其用途,但由于它不能提供线程安全保证,因此在实际编程中,开发者更倾向于使用互斥量、原子操作和其他同步机制来实现线程安全。
另言
有时候没有volatile也可能能正常运行,但是可能修改了编译器的优化级别之后就又不能正常运行了。
因此经常会出现debug版本正常,但是release版本却不能正常的问题。所以为了安全起见,只要是等待别的程序修改某个变量的话,就加上volatile关键字。
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。
但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,
总结
volatile关键字确实可以用于防止编译器优化,但它本身并不能保证线程安全。在实际编程中,开发者通常会使用互斥量、原子操作等同步机制来确保线程安全。在某些特殊情况下,volatile关键字可以与这些同步机制一起使用。
在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。
这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。
我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。