1 volatile
C/C++ 中的 volatile
关键字和 const
对应,用来修饰变量,通常用于建立语言级别的 memory barrier。这是 BS 在 "The C++ Programming Language" 对 volatile 修饰词的说明:
A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.
volatile
关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。应对场景:遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化。这是因为
volatile
提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile
关键字,则编译器可能优化读取和存储,就极有可能暂时使用寄存器中的值,此时这个变量由别的线程更新了的话,将出现不一致的现象示例:
int volatile vInt
; 当要求使用volatile
声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:
1.1 volatile的功能
(1)volatile可理解为“编译器警告指示字”
(2)volatile告诉编译器必须每次去内存中取变量值
(3)volatile主要修饰可能被多个线程访问的变量
(4)volatile也可以修饰可能被未知因数更改的变量
1.2 const volatile int i=0;
const volatile int i=0;
这是一个有趣的语句:
- 首先
const
修改i
,其被定义为一个常量,不能更改,只能初始化( “const”含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。) volatile
也修饰了i
,告诉编译器变量极有可能被未知因素更改,每次访问读值都有去内存取值( “volatile”的含义是“请不要做没谱的优化,这个值可能变掉的”,而并非“你可以修改这个值”。)
这两个使用并不矛盾,所以这里的i的属性是在本程序中,i应该是只读的,不应该被修改的,但是它也可能被外部的例如中断,共享的线程通过某种方式修改(如其他线程直接调用汇编去修改),所以这里也不该被编译器优化,虽然它是只读的不该被修改的,但是它还是会改变,我们在本程序中使用的时候,还是要每次都去读它的值,这是一种“双重保险”。
因此,const
和volatile
放在一起的意义在于:
- (1)本程序段中不能对a作修改,任何修改都是非法的,或者至少是粗心,编译器应该报错,防止这种粗心;
- (2)另一个程序段则完全有可能修改,因此编译器最好不要做太激进的优化。
1.3 volatile 指针
和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:
- 修饰由指针指向的对象、数据是
const
或volatile
的:1
2const char* cpch;
volatile char* vpch; - 指针自身的值——一个代表地址的整数变量,是
const
或volatile
的:1
2char* const pchc;
char* volatile pchv;
1.4 volatile不能保证线程安全
- 对于非原子操作,即使有
volatile
修饰,但也不能保证线程安全:输出:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16int volatile a = 0;
void fun() {
for (int j = 0; j < 100000; j++)
++a;
}
int main()
{
vector<thread> vec(10);
for (int i = 0; i < 10; ++i)
{
vec[i] = thread(fun);
}
for (auto& it : vec)
it.join();
cout << a << endl;
}1
267423
- 对于原子操作,volatile能够保证线程安全 输出
1
atomic<int> volatile a = 0;
1
1000000