在程序设计中,尤其是在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]" |