volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
当前回答
在标准C中,使用volatile的一个地方是在信号处理程序中。事实上,在标准C中,在信号处理程序中可以安全地做的就是修改易失性sig_atomic_t变量,或者快速退出。事实上,AFAIK,这是标准C中唯一需要使用volatile来避免未定义行为的地方。
ISO/IEC 9899:2011 §7.14.1.1 The signal function ¶5 If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t, or the signal handler calls any function in the standard library other than the abort function, the _Exit function, the quick_exit function, or the signal function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to the signal function results in a SIG_ERR return, the value of errno is indeterminate.252) 252) If any signal is generated by an asynchronous signal handler, the behavior is undefined.
这意味着在标准C中,你可以这样写:
static volatile sig_atomic_t sig_num = 0;
static void sig_handler(int signum)
{
signal(signum, sig_handler);
sig_num = signum;
}
除此之外就没什么了。
POSIX对于在信号处理程序中可以做的事情要宽容得多,但仍然存在限制(其中一个限制是标准I/O库- printf()等-不能安全地使用)。
其他回答
我应该提醒你的一个用法是,在信号处理函数中,如果你想访问/修改一个全局变量(例如,将其标记为exit = true),你必须将该变量声明为'volatile'。
Dan Saks在《Volatile as a promise》一文中写道:
易失性对象是其值可能自发变化的对象。也就是说,当你声明一个对象为volatile时,你是在告诉编译器这个对象可能会改变状态,即使程序中没有任何语句显示要改变它。”
以下是他关于volatile关键字的三篇文章的链接:
明智地使用挥发剂 准确放置挥发剂 像承诺一样反复无常
为嵌入式开发,我有一个循环,检查可以在中断处理程序中更改的变量。如果没有“volatile”,循环就变成了noop——就编译器所知,变量永远不会改变,所以它优化了检查。
同样的事情也适用于在更传统的环境中可能在不同线程中更改的变量,但在那里我们经常进行同步调用,因此编译器在优化方面没有那么自由。
我曾经在调试构建中使用过它,当编译器坚持要优化掉一个变量时,我希望在逐步执行代码时能够看到这个变量。
所有的答案都很好。但在此之上,我想分享一个例子。
下面是一个小的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。