C++生僻字的基本作用与用法(volatile)

volatile

volatile关键字是防止在共享的空间发生读取的错误。只保证其可见性,不保证原子性;使用volatile指每次从内存中读取数据,而不是从编译器优化后的缓存中读取数据,简单来讲就是防止编译器优化。

  在单任务环境中,如果在两次读取变量之间不改变变量的值,编译器就会发生优化,会将RAM中的值赋值到寄存器中;由于访问寄存器的效率要高于RAM,所以在需要读取变量时,直接寄存器中获取变量的值,而不是从RAM中。

  在多任务环境中,虽然在两次读取变量之间不改变变量的值,在一些情况下变量的值还是会发生改变,比如在发生中断程序或者有其他的线程。这时候如果编译器优化,依旧从寄存器中获取变量的值,修改的值就得不到及时的响应(在RAM还未将新的值赋值给寄存器,就已经获取到寄存器的值)。

  要想防止编译器优化,就需要在声明变量时加volatile关键字,加关键字后,就在RAM中读取变量的值,而不是直接在寄存器中取值。

一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

volatile修饰指针

和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:

修饰由指针指向的对象、数据是 const 或 volatile 的:

  1. const *char** cpch;
  2. volatile *char** vpch;
1
2
const char* cpch;
volatile char* vpch;

注意:对于 VC,这个特性实现在 VC 8 之后才是安全的。

指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:

  1. *char****const** pchc;
  2. *char****volatile** pchv;
1
2
3
char*const pchc;

char*volatile pchv;

⚠️

(1) 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。

(2) 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
(3) C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

3. 多线程下的volatile

有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:

volatile BOOL bStop = FALSE;

// 在一个线程中:

while( !bStop ) { … }

bStop = FALSE;

return;

//在另外一个线程中,要终止上面的线程循环:

bStop = TRUE;

while( bStop ); //等待上面的线程终止,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
volatile  BOOL  bStop  =  FALSE;  







// 在一个线程中:



while( !bStop ) { ... }



bStop = FALSE;



return;







//在另外一个线程中,要终止上面的线程循环:



bStop = TRUE;



while( bStop ); //等待上面的线程终止,

如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。
这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:

1
2
3
4
**int** nMyCounter = 0;  
**for**(; nMyCounter<100;nMyCounter++)
{
}
1
2
3
4
5
6
7
int  nMyCounter  =  0;  

for(; nMyCounter<100;nMyCounter++)

{
...
}

在此段代码中,nMyCounter的拷贝可能存放到某个寄存器中(循环中,对nMyCounter的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1;这个操作中,对nMyCounter的改变是对内存中的nMyCounter进行操作,于是出现了这样一个现象:nMyCounter的改变不同步。