volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
当前回答
volatile关键字的目的是防止编译器对可能以编译器无法确定的方式改变的对象应用任何优化。
声明为volatile的对象在优化中被省略,因为它们的值可以被当前代码范围之外的代码随时更改。系统总是从内存位置读取volatile对象的当前值,而不是在请求时将其值保存在临时寄存器中,即使之前的指令从同一对象中请求一个值。
考虑以下情况
1)由中断服务例程在作用域外修改的全局变量。
2)多线程应用程序中的全局变量。
如果不使用volatile限定符,可能会出现以下问题
1)当优化被打开时,代码可能不会像预期的那样工作。
2)当中断被启用和使用时,代码可能不会像预期的那样工作。
Volatile:程序员最好的朋友
https://en.wikipedia.org/wiki/Volatile_ (computer_programming)
其他回答
所有的答案都很好。但在此之上,我想分享一个例子。
下面是一个小的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。
我想引用Herb Sutter在GotW #95中的一句话,这有助于理解volatile变量的含义:
C++ volatile variables (which have no analog in languages like C# and Java) are always beyond the scope of this and any other article about the memory model and synchronization. That’s because C++ volatile variables aren’t about threads or communication at all and don’t interact with those things. Rather, a C++ volatile variable should be viewed as portal into a different universe beyond the language — a memory location that by definition does not obey the language’s memory model because that memory location is accessed by hardware (e.g., written to by a daughter card), have more than one address, or is otherwise “strange” and beyond the language. So C++ volatile variables are universally an exception to every guideline about synchronization because are always inherently “racy” and unsynchronizable using the normal tools (mutexes, atomics, etc.) and more generally exist outside all normal of the language and compiler including that they generally cannot be optimized by the compiler (because the compiler isn’t allowed to know their semantics; a volatile int vi; may not behave anything like a normal int, and you can’t even assume that code like vi = 5; int read_back = vi; is guaranteed to result in read_back == 5, or that code like int i = vi; int j = vi; that reads vi twice will result in i == j which will not be true if vi is a hardware counter for example).
为嵌入式开发,我有一个循环,检查可以在中断处理程序中更改的变量。如果没有“volatile”,循环就变成了noop——就编译器所知,变量永远不会改变,所以它优化了检查。
同样的事情也适用于在更传统的环境中可能在不同线程中更改的变量,但在那里我们经常进行同步调用,因此编译器在优化方面没有那么自由。
我应该提醒你的一个用法是,在信号处理函数中,如果你想访问/修改一个全局变量(例如,将其标记为exit = true),你必须将该变量声明为'volatile'。
在开发嵌入式系统或设备驱动程序时,需要使用Volatile,因为在这些驱动程序中需要读写内存映射的硬件设备。特定设备寄存器的内容随时都可能改变,所以你需要volatile关键字来确保这样的访问不会被编译器优化。