volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
当前回答
所有的答案都很好。但在此之上,我想分享一个例子。
下面是一个小的cpp程序:
#include <iostream>
int x;
int main(){
char buf[50];
x = 8;
if(x == 8)
printf("x is 8\n");
else
sprintf(buf, "x is not 8\n");
x=1000;
while(x > 5)
x--;
return 0;
}
现在,让我们生成上述代码的程序集(我将只粘贴与此相关的程序集的部分):
生成程序集的命令:
g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp
会众是这样。
main:
.LFB1594:
subq $40, %rsp #,
.seh_stackalloc 40
.seh_endprologue
# assembly.cpp:5: int main(){
call __main #
# assembly.cpp:10: printf("x is 8\n");
leaq .LC0(%rip), %rcx #,
# assembly.cpp:7: x = 8;
movl $8, x(%rip) #, x
# assembly.cpp:10: printf("x is 8\n");
call _ZL6printfPKcz.constprop.0 #
# assembly.cpp:18: }
xorl %eax, %eax #
movl $5, x(%rip) #, x
addq $40, %rsp #,
ret
.seh_endproc
.p2align 4,,15
.def _GLOBAL__sub_I_x; .scl 3; .type 32; .endef
.seh_proc _GLOBAL__sub_I_x
您可以在程序集中看到,没有为sprintf生成程序集代码,因为编译器假定x不会在程序之外发生变化。while循环也是如此。由于优化,循环被完全删除,因为编译器认为它是无用的代码,因此直接将5分配给x(参见movl $5, x(%rip))。
如果外部进程/硬件将x的值更改为x = 8之间的某个值,则会出现问题;和if(x == 8).我们希望else块可以工作,但不幸的是编译器已经删除了这部分。
现在,为了解决这个问题,在assembly。cpp中,让我们改变int x;到volatile int x;并快速查看生成的汇编代码:
main:
.LFB1594:
subq $104, %rsp #,
.seh_stackalloc 104
.seh_endprologue
# assembly.cpp:5: int main(){
call __main #
# assembly.cpp:7: x = 8;
movl $8, x(%rip) #, x
# assembly.cpp:9: if(x == 8)
movl x(%rip), %eax # x, x.1_1
# assembly.cpp:9: if(x == 8)
cmpl $8, %eax #, x.1_1
je .L11 #,
# assembly.cpp:12: sprintf(buf, "x is not 8\n");
leaq 32(%rsp), %rcx #, tmp93
leaq .LC0(%rip), %rdx #,
call _ZL7sprintfPcPKcz.constprop.0 #
.L7:
# assembly.cpp:14: x=1000;
movl $1000, x(%rip) #, x
# assembly.cpp:15: while(x > 5)
movl x(%rip), %eax # x, x.3_15
cmpl $5, %eax #, x.3_15
jle .L8 #,
.p2align 4,,10
.L9:
# assembly.cpp:16: x--;
movl x(%rip), %eax # x, x.4_3
subl $1, %eax #, _4
movl %eax, x(%rip) # _4, x
# assembly.cpp:15: while(x > 5)
movl x(%rip), %eax # x, x.3_2
cmpl $5, %eax #, x.3_2
jg .L9 #,
.L8:
# assembly.cpp:18: }
xorl %eax, %eax #
addq $104, %rsp #,
ret
.L11:
# assembly.cpp:10: printf("x is 8\n");
leaq .LC1(%rip), %rcx #,
call _ZL6printfPKcz.constprop.1 #
jmp .L7 #
.seh_endproc
.p2align 4,,15
.def _GLOBAL__sub_I_x; .scl 3; .type 32; .endef
.seh_proc _GLOBAL__sub_I_x
在这里,您可以看到生成了sprintf、printf和while循环的程序集代码。这样做的好处是,如果某个外部程序或硬件更改了x变量,那么将执行sprintf部分的代码。类似地,while循环也可以用于busy waiting now。
其他回答
您必须使用它来实现自旋锁以及一些(所有?)无锁数据结构 与原子操作/指令一起使用 曾经帮助我克服编译器的错误(在优化过程中错误地生成代码)
在开发嵌入式系统或设备驱动程序时,需要使用Volatile,因为在这些驱动程序中需要读写内存映射的硬件设备。特定设备寄存器的内容随时都可能改变,所以你需要volatile关键字来确保这样的访问不会被编译器优化。
在实现无锁数据结构时必须使用volatile。否则,编译器可以自由地优化对变量的访问,这将改变语义。
换句话说,volatile告诉编译器对这个变量的访问必须对应于物理内存的读/写操作。
例如,这是Win32 API中InterlockedIncrement的声明方式:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile *Addend
);
我曾经在20世纪90年代早期开发过一个大型应用程序,其中包含使用setjmp和longjmp进行基于c语言的异常处理。volatile关键字对于那些值需要保存在作为“catch”子句的代码块中的变量是必要的,以免这些变量被存储在寄存器中并被longjmp清除。
你的程序似乎工作,即使没有挥发关键字?也许这就是原因:
如前所述,volatile关键字有助于以下情况
volatile int* p = ...; // point to some memory
while( *p!=0 ) {} // loop until the memory becomes zero
但是,一旦调用外部函数或非内联函数,似乎几乎没有任何影响。例如:
while( *p!=0 ) { g(); }
然后无论是否使用volatile都会产生几乎相同的结果。
只要g()可以完全内联,编译器就可以看到正在发生的一切,因此可以进行优化。但是,当程序调用一个编译器看不到发生什么的地方时,编译器再做任何假设就不安全了。因此,编译器生成的代码总是直接从内存中读取。
但是要注意,当函数g()变成内联(由于显式更改或由于编译器/链接器的聪明)时,如果您忘记volatile关键字,那么您的代码可能会崩溃!
因此,我建议添加volatile关键字,即使您的程序似乎没有它也可以工作。它使意图在未来的变化方面更加清晰和强大。