Volatile变量
在程序设计中,尤其是在C语言、C++、C#和Java语言中,使用volatile关键字声明的变量或对象通常拥有和优化和(或)多线程相关的特殊属性。通常,volatile关键字用来阻止(伪)编译器对那些它认为变量的值不能“被代码本身”改变的代码上执行任何优化。 在C环境中,volatile关键字的真实定义和适用范围经常被误解,加之C++、C#和Java都从C中神秘地“继承”了volatile,在这些编程语言中,因此这些语言中volatile的用法和语义大相径庭。
目录 |
C和C++中的volatile [编辑]
在C,以及C++中,volatile关键字的作用[1]:
- 允许访问内存映射设备
- 允许在
setjmp和longjmp之间使用变量 - 允许在信号处理函数中使用sig_atomic_t变量
根据相关的标准(C,C++,POSIX,WIN32)和目前绝大多数实现,对volatile变量的操作并不是原子的,也不能用来为线程建立严格的happens-before关系。volatile关键字就像便携式线程构建一样基本没什么用处[2][3][4][5][6]。
C语言中MMIO的例子 [编辑]
在这里例子中,代码将foo的值设置为0。然后开始不断地轮询它的值直到它变成255:
static int foo; void bar(void) { foo = 0; while (foo != 255) ; }
一个执行优化的编译器会提示没有代码能修改foo的值,并假设它永远都只会是0.因此编译器将用类似下列的无限循环替换函数体:
void bar_optimized(void) { foo = 0; while (true) ; }
但是,foo可能指向一个随时都能被计算机系统其他部分修改的地址,例如一个连接到中央处理器的设备的硬體暫存器,上面的代码永远检测不到这样的修改。如果不使用volatile关键字,编译器将假设当前程序是系统中唯一能改变这个值部分(这是到目前为止最广泛的一种情况)。 为了阻止编译器像上面那样优化代码,需要使用volatile关键字:
static volatile int foo; void bar (void) { foo = 0; while (foo != 255) ; }
这样修改以后循环条件就不会被优化掉,当值改变的时候系统将会检测到。
C语言中的优化对比 [编辑]
下面的C程序和后面的汇编代码展示了volatile关键字如何影响编译器的输出。这里使用的编译器是GCC。
| 汇编对照 | |
|---|---|
| 不使用volatile | 使用volatile |
#include <stdio.h> int main() { int a = 10, b = 100, c = 0, d = 0; printf("%d", a + b); a = b; c = b; d = b; printf("%d", c + d); return 0; } |
#include <stdio.h> int main() { volatile int a = 10, b = 100, c = 0, d = 0; printf("%d", a + b); a = b; c = b; d = b; printf("%d", c + d); return 0; } |
| gcc -O3 -S without.c -o without.s | gcc -S with.c -o with.s |
.file "without.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
movl $110, 4(%esp)
movl $.LC0, (%esp)
call printf
movl $200, 4(%esp)
movl $.LC0, (%esp)
call printf
addl $20, %esp
xorl %eax, %eax
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (GNU) 4.2.1 20070719 [FreeBSD]"
|
.file "with.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $36, %esp
movl $10, -8(%ebp)
movl $100, -12(%ebp)
movl $0, -16(%ebp)
movl $0, -20(%ebp)
movl -8(%ebp), %edx
movl -12(%ebp), %eax
movl $.LC0, (%esp)
addl %edx, %eax
movl %eax, 4(%esp)
call printf
movl -12(%ebp), %eax
movl %eax, -8(%ebp)
movl -12(%ebp), %eax
movl %eax, -16(%ebp)
movl -12(%ebp), %eax
movl %eax, -20(%ebp)
movl -16(%ebp), %edx
movl -20(%ebp), %eax
movl $.LC0, (%esp)
addl %edx, %eax
movl %eax, 4(%esp)
call printf
addl $36, %esp
xorl %eax, %eax
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (GNU) 4.2.1 20070719 [FreeBSD]"
|
Java中的volatile [编辑]
Java也支持volatile关键字,但它被用于其他不同的用途。当volatile用于一个作用域时,Java保证如下:
- (适用于Java所有版本)读和写一个volatile变量有全局的排序。也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。(但是并不保证经常读写volatile作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。
- (适用于Java5及其之后的版本)volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁[7]。
使用volatile会比使用锁更快,但是在一些情况下它不能工作。volatile使用范围在Java5中得到了扩展,特别是双重检查锁定现在能够正确工作[8]。
Ada中的volatile [编辑]
在Ada中,比起关键字,Volatile标记更像是一种指令。“对于volatile对象而言,所有读和更新都会作为一个整体直接执行到内存”[9]。
参考 [编辑]
- ^ Publication on C++ standards committee website; http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html
- ^ Publication on C++ standards committee website; http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html
- ^ Volatile Keyword In Visual C++; http://msdn2.microsoft.com/en-us/library/12a04hfd.aspx
- ^ Linux Kernel Documentation - Why the "volatile" type class should not be used; http://kernel.org/doc/Documentation/volatile-considered-harmful.txt
- ^ Volatile: Almost Useless for Multi-Threaded Programming (Intel Software Network); http://softwareblogs.intel.com/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/
- ^ C++ and the Perils of Double-Checked Locking; http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
- ^ Section 17.4.4: Synchronization Order The Java Language Specification, 3rd Edition. Sun Microsystems. 2005 [2010-11-22].
- ^ Neil Coffey. Double-checked Locking (DCL) and how to fix it. Javamex. [2009-09-19].
- ^ "C.6 Shared Variable Control" Ada Reference Manual. ISO. 2005 [2010-05-04].