volatile关键字的作用是什么?在c++中它能解决什么问题?

就我而言,我从来没有明知肚明地需要它。


当前回答

我想引用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).

其他回答

在标准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()等-不能安全地使用)。

为嵌入式开发,我有一个循环,检查可以在中断处理程序中更改的变量。如果没有“volatile”,循环就变成了noop——就编译器所知,变量永远不会改变,所以它优化了检查。

同样的事情也适用于在更传统的环境中可能在不同线程中更改的变量,但在那里我们经常进行同步调用,因此编译器在优化方面没有那么自由。

您必须使用它来实现自旋锁以及一些(所有?)无锁数据结构 与原子操作/指令一起使用 曾经帮助我克服编译器的错误(在优化过程中错误地生成代码)

其他答案已经提到避免一些优化,以便:

使用内存映射寄存器(或MMIO) 写入设备驱动程序 允许更容易的程序调试 使浮点计算更具确定性

当你需要一个值看起来来自外部,不可预测,避免编译器基于已知值进行优化时,当一个结果实际上没有使用,但你需要计算它时,或者当它被使用,但你想要计算它几次作为基准时,你需要计算在精确的点开始和结束时,Volatile是必不可少的。

volatile read类似于输入操作(如scanf或cin的使用):该值似乎来自程序外部,因此任何依赖于该值的计算都需要在它之后开始。

volatile写类似于输出操作(如printf或cout的使用):值似乎是在程序外部传递的,因此如果值依赖于计算,则需要在之前完成。

因此,一对易变的读/写可以用来控制基准测试,使时间测量变得有意义。

如果没有volatile,你的计算可以在编译器之前启动,因为没有什么会阻止计算与时间测量等函数的重新排序。

所有的答案都很好。但在此之上,我想分享一个例子。

下面是一个小的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。