含义

对于程序员来说,程序本身的任何行为都必须是可预期的。那么,在程序当中,什么才叫 volatile 呢?这个问题的答案也很简单:程序可能受到程序之外的因素影响。

多线程

volatile 在 c90 时代就有了,当时 c 标准库还没有对并发程序支持,volatile 不是为了解决线程安全诞生的。volatile 修饰的变量将会读写到内存的一块区域,而不是一块 cpu 的高速缓存中。它保证了各个线程读取的是同一块位置。

image.png
当多线程的程序用同一个指针去修改值之后,需要CPU去讲L1中的数据同步到L2,然后从L2再同步到L1。此时如果用两个指针去修改同一个地址,那么两个指针不一定会去读同一块地方。只有变量被volatile修饰了,该变量所在的缓存行才被赋予缓存一致性的校验功能。

  1. int a = 0;
  2. int *p = &a;
  3. volatile int *q = &a;

加了 volatile 修饰的 q 指针,我们每次通过 q 这个途径去访问 a 对象的时候,都会直接去内存中去读。

解决代码合并

对 volatile 对象的访问,有编译器优化上的副作用,不允许被优化消失(optimized out),于序列上在另一个对 volatile 对象的访问之前。

  1. #include <iostream>
  2. int main() {
  3. int a = 10000;
  4. a+= 1000;
  5. a+= 2000;
  6. std::cout << a;
  7. return 0;
  8. }
  9. // gcc -S main.cpp -o main.s -O2
  10. movl $13000, %edx
  1. #include <iostream>
  2. int main() {
  3. volatile int a = 10000;
  4. a+= 1000;
  5. a+= 2000;
  6. std::cout << a;
  7. return 0;
  8. }
  9. // gcc -S main.cpp -o main.s -O2
  10. movl $10000, 44(%rsp)
  11. movl 44(%rsp), %eax
  12. movq .refptr._ZSt4cout(%rip), %rcx
  13. addl $1000, %eax
  14. movl %eax, 44(%rsp)
  15. movl 44(%rsp), %eax
  16. addl $2000, %eax