什么是 volatile
volatile (易变型变量)是一个类型修饰符
volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值
volatile 的作用
volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值,简单地说就是防止编译器对代码进行优化
用它声明的类型变量表示变量本身可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
2)禁止进行指令重排序,即以下情况
- 当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面包含它本身的操作可见,在其后面的操作肯定还没有进行;
- 在进行指令优化时,不能将在对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行
- volatile 变量访问的语句只能确保前后语句段完全在 volatile 变量访问的语句前后执行,但不确保前面的语句段或后面的语句段的各个语句按序执行
3)如果是写操作,它会导致其他CPU中对应的缓存行无效
volatile 的运用场景
优化器在用到这个变量时必须每次都重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是 volatile 变量的几个例子:
-
并行设备的硬件寄存器(如:状态寄存器)
-
一个中断服务子程序中会访问到的非自动变量
-
多线程应用中被几个任务共享的变量
所以,一般情况 volatile 用在如下的几个地方:
-
存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能有不同意义
-
中断服务程序中修改的供其它程序检测的变量需要加 volatile
-
多任务环境下各任务间共享的标志应该加 volatile
另外,以上这几种情况还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以依靠硬件的良好设计来实现,2 中可以通过关中断来实现,3中可以禁止任务调度
嵌入式系统程序员在硬件、中断、RTOS等都要求使用 volatile 变量
volatile 不可运用的场景
- 用于做计数器
- 与其他变量构成不变式
- 对变量的写操作依赖于当前值
volatile 关键字无法保证操作的原子性,所以尽管 volatile 关键字在某些情况下性能要优于synchronized,但是 volatile 关键字无法替代 synchronized 关键字
常见疑问
1)一个参数既可以是 const 还可以是 volatile 吗?
2)一个指针可以是 volatile 吗?
下面是答案:
1)是。举个例子:只读的状态寄存器,它是 volatile 因为它可能被意想不到地改变,同时它是 const 因为程序不应该去修改它。
2)是。尽管不常见,同样的举个例子:当一个中断服务子程序修改一个指向一个 buffer 的指针时
volatile 实现过程
当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据,而且读取的数据立刻被保存