为什么存在挥发性?

发布于 2024-07-05 01:02:32 字数 82 浏览 7 评论 0原文

volatile 关键字有什么作用? 在C++中它解决了什么问题?

就我而言,我从来没有故意需要它。

What does the volatile keyword do? In C++ what problem does it solve?

In my case, I have never knowingly needed it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

草莓酥 2024-07-12 01:02:32

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

  • 使用内存映射寄存器(或“MMIO”)
  • 编写设备驱动程序
  • 允许更轻松地调试程序
  • 使浮点计算更具确定性

每当您需要一个值看起来来自外部时,易失性都是必不可少的并且是不可预测的,并避免基于已知值进行编译器优化,并且当实际未使用结果但您需要计算它时,或者使用它但您想多次计算它以进行基准测试时,您需要计算在精确点开始和结束。

易失性读取就像输入操作(例如 scanfcin 的使用):值似乎来自程序外部,因此任何计算依赖于该值的需要在它之后开始

易失性写入类似于输出操作(例如 printf 或使用 cout):该值似乎是在程序外部传递的,因此如果该值取决于计算,需要先完成

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

如果没有易失性,您的计算可以由编译器之前启动,因为没有什么可以阻止使用时间测量等函数重新排序

Other answers already mention avoiding some optimization in order to:

  • use memory mapped registers (or "MMIO")
  • write device drivers
  • allow easier debugging of programs
  • make floating point computations more deterministic

Volatile is essential whenever you need a value to appear to come from the outside and be unpredictable and avoid compiler optimizations based on a value being known, and when a result isn't actually used but you need it to be computed, or it's used but you want to compute it several times for a benchmark, and you need the computations to start and end at precise points.

A volatile read is like an input operation (like scanf or a use of cin): the value seems to come from the outside of the program, so any computation that has a dependency on the value needs to start after it.

A volatile write is like an output operation (like printf or a use of cout): the value seems to be communicated outside of the program, so if the value depends on a computation, it needs to be finished before.

So a pair of volatile read/write can be used to tame benchmarks and make time measurement meaningful.

Without volatile, your computation could be started by the compiler before, as nothing would prevent reordering of computations with functions such as time measurement.

送舟行 2024-07-12 01:02:32

我应该提醒您的一个用途是,在信号处理函数中,如果您想访问/修改全局变量(例如,将其标记为 exit = true),则必须将该变量声明为“易失性”。

One use I should remind you is, in the signal handler function, if you want to access/modify a global variable (for example, mark it as exit = true) you have to declare that variable as 'volatile'.

你与昨日 2024-07-12 01:02:32

在 C 的早期,编译器会将所有读取和写入左值的操作解释为内存操作,并按照与代码中出现的读取和写入相同的顺序执行。 如果给予编译器一定的自由度来重新排序和合并操作,在许多情况下效率可以大大提高,但这样做存在一个问题。 尽管操作经常以某种顺序指定,仅仅是因为有必要以某种顺序指定它们,因此程序员选择了许多同样好的替代方案之一,但情况并非总是如此。 有时某些操作按特定顺序发生很重要。

到底哪些测序细节是重要的,取决于目标平台和应用领域。 该标准没有提供特别详细的控制,而是选择了一个简单的模型:如果一系列访问是使用不合格的易失性的左值完成的,则编译器可能会根据需要对它们进行重新排序和合并。 如果某个操作是使用 易失性 限定的左值完成的,则质量实现应该提供针对其预期平台和应用程序领域的代码可能需要的任何附加排序保证,而不要求程序员使用非标准语法。

不幸的是,许多编译器没有确定程序员需要什么保证,而是选择提供标准规定的最低限度的保证。 这使得易失性的用处远没有应有的那么大。 例如,在 gcc 或 clang 上,需要实现基本“移交互斥体”的程序员必须执行以下操作:已获取并释放互斥体的任务在另一个任务完成之前不会再次执行此操作)有四件事:

  1. 将互斥体的获取和释放放在编译器无法内联且无法应用全程序优化的函数中。

  2. 将互斥体保护的所有对象限定为易失性——如果所有访问都发生在获取互斥体之后和释放它之前,则不需要这样做。

  3. 使用优化级别 0 强制编译器生成代码,就好像所有未经过注册的对象都是易失性

  4. 使用特定于 gcc 的指令。

相比之下,当使用更适合系统编程的更高质量的编译器(例如 icc)时,人们将有另一种选择:

  1. 确保在每次获取或释放时都执行 易失性 限定的写入是需要的。

获取基本的“交接互斥体”需要进行 易失性读取(以查看它是否已准备好),并且不应该需要进行易失性写入(另一方获胜)在将其交还之前不要尝试重新获取它),但必须执行无意义的 易失性 写入仍然比 gcc 或 clang 下可用的任何选项更好。

In the early days of C, compilers would interpret all actions that read and write lvalues as memory operations, to be performed in the same sequence as the reads and writes appeared in the code. Efficiency could be greatly improved in many cases if compilers were given a certain amount of freedom to re-order and consolidate operations, but there was a problem with this. Even though operations were often specified in a certain order merely because it was necessary to specify them in some order, and thus the programmer picked one of many equally-good alternatives, that wasn't always the case. Sometimes it would be important that certain operations occur in a particular sequence.

Exactly which details of sequencing are important will vary depending upon the target platform and application field. Rather than provide particularly detailed control, the Standard opted for a simple model: if a sequence of accesses are done with lvalues that are not qualified volatile, a compiler may reorder and consolidate them as it sees fit. If an action is done with a volatile-qualified lvalue, a quality implementation should offer whatever additional ordering guarantees might be required by code targeting its intended platform and application field, without requiring that programmers use non-standard syntax.

Unfortunately, rather than identify what guarantees programmers would need, many compilers have opted instead to offer the bare minimum guarantees mandated by the Standard. This makes volatile much less useful than it should be. On gcc or clang, for example, a programmer needing to implement a basic "hand-off mutex" [one where a task that has acquired and released a mutex won't do so again until the other task has done so] must do one of four things:

  1. Put the acquisition and release of the mutex in a function that the compiler cannot inline, and to which it cannot apply Whole Program Optimization.

  2. Qualify all the objects guarded by the mutex as volatile--something which shouldn't be necessary if all accesses occur after acquiring the mutex and before releasing it.

  3. Use optimization level 0 to force the compiler to generate code as though all objects that aren't qualified register are volatile.

  4. Use gcc-specific directives.

By contrast, when using a higher-quality compiler which is more suitable for systems programming, such as icc, one would have another option:

  1. Make sure that a volatile-qualified write gets performed everyplace an acquire or release is needed.

Acquiring a basic "hand-off mutex" requires a volatile read (to see if it's ready), and shouldn't require a volatile write as well (the other side won't try to re-acquire it until it's handed back) but having to perform a meaningless volatile write is still better than any of the options available under gcc or clang.

梦巷 2024-07-12 01:02:32

除了 volatile 关键字用于告诉编译器不要优化对某些变量(可以由线程或中断例程修改)的访问之外,它还可以用于消除一些编译器错误 -- 是的 ---。

例如,我在嵌入式平台上工作,编译器对变量的值做出了一些错误的假设。 如果代码没有优化,程序将正常运行。 通过优化(确实需要优化,因为这是一个关键的例程),代码将无法正常工作。 唯一的解决方案(尽管不是很正确)是将“错误”变量声明为易失性。

Beside the fact that the volatile keyword is used for telling the compiler not to optimize the access to some variable (that can be modified by a thread or an interrupt routine), it can be also used to remove some compiler bugs -- YES it can be ---.

For example I worked on an embedded platform were the compiler was making some wrong assuptions regarding a value of a variable. If the code wasn't optimized the program would run ok. With optimizations (which were really needed because it was a critical routine) the code wouldn't work correctly. The only solution (though not very correct) was to declare the 'faulty' variable as volatile.

人生百味 2024-07-12 01:02:32

即使没有 volatile 关键字,您的程序似乎也能工作? 也许这就是原因:

正如前面提到的,易失性关键字有助于解决类似的情况,

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

但一旦调用外部或非内联函数,似乎几乎没有效果。 例如:

while( *p!=0 ) { g(); }

无论有或没有易失性,都会生成几乎相同的结果。

只要 g() 可以完全内联,编译器就可以看到正在发生的一切,从而可以进行优化。 但是,当程序调用编译器无法看到正在发生的情况的地方时,编译器再做出任何假设都是不安全的。 因此,编译器将生成始终直接从内存读取的代码。

但请注意,当您的函数 g() 变为内联(由于显式更改或由于编译器/链接器的聪明才智)时,如果您忘记了 volatile 关键字,您的代码可能会崩溃!

因此,即使您的程序看起来没有关键字也能正常工作,我还是建议添加 volatile 关键字。 它使未来变化的意图更加清晰、更加有力。

Your program seems to work even without volatile keyword? Perhaps this is the reason:

As mentioned previously the volatile keyword helps for cases like

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

But there seems to be almost no effect once an external or non-inline function is being called. E.g.:

while( *p!=0 ) { g(); }

Then with or without volatile almost the same result is generated.

As long as g() can be completely inlined, the compiler can see everything that's going on and can therefore optimize. But when the program makes a call to a place where the compiler can't see what's going on, it isn't safe for the compiler to make any assumptions any more. Hence the compiler will generate code that always reads from memory directly.

But beware of the day, when your function g() becomes inline (either due to explicit changes or due to compiler/linker cleverness) then your code might break if you forgot the volatile keyword!

Therefore I recommend to add the volatile keyword even if your program seems to work without. It makes the intention clearer and more robust in respect to future changes.

空袭的梦i 2024-07-12 01:02:32

我想引用 Herb Sutter 的话 GotW #95,可以帮助理解易失性变量的含义:

C++ 易失性变量(在C#Java等语言中没有类似的变量)总是超出了本文以及任何其他有关内存模型和同步的文章的范围。 这是因为 C++ 易失性 变量根本与线程或通信无关,并且不与这些事物交互。 相反,C++ 易失性 变量应该被视为进入语言之外的不同宇宙的门户 - 根据定义,该内存位置不遵守语言的内存模型,因为该内存位置由硬件访问(例如,由子卡写入),具有多个地址,或者是“奇怪的”并且超出了语言范围。 因此,C++ 易失性 变量对于所有有关同步的准则来说都是普遍的例外,因为它本质上总是“活泼的”并且无法使用普通工具(互斥体、原子等)进行同步等等。通常存在于语言和编译器的所有正常范围之外,包括它们通常无法被编译器优化(因为不允许编译器知道它们的语义;volatile int vi; 可能不会表现出类似的行为一个普通的 int,你甚至不能假设像 vi = 5; int read_back = vi; 这样的代码一定会导致 read_back == 5< /code>,或者像 int i = vi; int j = vi; 这样读取 vi 两次的代码将导致 i == j 如果 <例如,code>vi 是一个硬件计数器)。


I would like to quote Herb Sutter's words from his GotW #95, which can help to understand the meaning of the volatile variables:

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).

会傲 2024-07-12 01:02:32

所有答案都非常好。 但最重要的是,我想分享一个例子。

下面是一个小 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 循环的情况也是如此。 由于优化,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

在这里您可以看到生成了 sprintfprintfwhile 循环的汇编代码。 优点是如果x变量被某些外部程序或硬件更改,sprintf部分代码将被执行。 类似地,while 循环可用于现在忙等待。

All answers are excellent. But on the top of that, I would like to share an example.

Below is a little cpp program:

#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;
}

Now, lets generate the assembly of the above code (and I will paste only that portions of the assembly which relevant here):

The command to generate assembly:

g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp

And the assembly:

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

You can see in the assembly that the assembly code was not generated for sprintf because the compiler assumed that x will not change outside of the program. And same is the case with the while loop. while loop was altogether removed due to the optimization because compiler saw it as a useless code and thus directly assigned 5 to x (see movl $5, x(%rip)).

The problem occurs when what if an external process/ hardware would change the value of x somewhere between x = 8; and if(x == 8). We would expect else block to work but unfortunately the compiler has trimmed out that part.

Now, in order to solve this, in the assembly.cpp, let us change int x; to volatile int x; and quickly see the assembly code generated:

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

Here you can see that the assembly codes for sprintf, printf and while loop were generated. The advantage is that if the x variable is changed by some external program or hardware, sprintf part of the code will be executed. And similarly while loop can be used for busy waiting now.

东走西顾 2024-07-12 01:02:32

volatile 关键字旨在防止编译器对可能以编译器无法确定的方式更改的对象应用任何优化。

声明为易失性的对象在优化中被忽略,因为它们的值可以随时被当前代码范围之外的代码更改。 系统始终从内存位置读取易失性对象的当前值,而不是在请求时将其值保留在临时寄存器中,即使先前的指令要求来自同一对象的值。

考虑以下情况

1) 全局变量被作用域外的中断服务程序修改。

2) 多线程应用程序中的全局变量。

如果我们不使用 volatile 限定符,可能会出现以下问题

1)打开优化后,代码可能无法按预期工作。

2) 当启用和使用中断时,代码可能无法按预期工作。

Volatile:程序员最好的朋友

https://en.wikipedia.org/wiki/Volatile_(computer_programming)

The volatile keyword is intended to prevent the compiler from applying any optimisations on objects that can change in ways that cannot be determined by the compiler.

Objects declared as volatile are omitted from optimisation because their values can be changed by code outside the scope of current code at any time. The system always reads the current value of a volatile object from the memory location rather than keeping its value in temporary register at the point it is requested, even if a previous instruction asked for a value from the same object.

Consider the following cases

1) Global variables modified by an interrupt service routine outside the scope.

2) Global variables within a multi-threaded application.

If we do not use volatile qualifier, the following problems may arise

1) Code may not work as expected when optimisation is turned on.

2) Code may not work as expected when interrupts are enabled and used.

Volatile: A programmer’s best friend

https://en.wikipedia.org/wiki/Volatile_(computer_programming)

慕烟庭风 2024-07-12 01:02:32
  1. 你必须使用它来实现自旋锁以及一些(全部?)无锁数据结构将
  2. 它与原子操作/指令一起使用
  3. 曾经帮助我克服了编译器的错误(优化期间错误生成的代码)
  1. you must use it to implement spinlocks as well as some (all?) lock-free data structures
  2. use it with atomic operations/instructions
  3. helped me once to overcome compiler's bug (wrongly generated code during optimization)
夏见 2024-07-12 01:02:32

在针对嵌入式开发时,我有一个循环来检查可以在中断处理程序中更改的变量。 如果没有“易失性”,循环就会变成空操作 - 据编译器所知,变量永远不会改变,因此它会优化检查。

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

Developing for an embedded, I have a loop that checks on a variable that can be changed in an interrupt handler. Without "volatile", the loop becomes a noop - as far as the compiler can tell, the variable never changes, so it optimizes the check away.

Same thing would apply to a variable that may be changed in a different thread in a more traditional environment, but there we often do synchronization calls, so compiler is not so free with optimization.

救赎№ 2024-07-12 01:02:32

当编译器坚持优化我希望在单步执行代码时能够看到的变量时,我在调试版本中使用了它。

I've used it in debug builds when the compiler insists on optimizing away a variable that I want to be able to see as I step through code.

舞袖。长 2024-07-12 01:02:32

除了按预期使用它之外,易失性还用于(模板)元编程。 它可用于防止意外重载,因为 volatile 属性(如 const)参与重载决策。

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

这是合法的; 两个重载都可能是可调用的并且执行几乎相同的操作。 易失性 重载中的强制转换是合法的,因为我们知道 bar 无论如何都不会传递非易失性 T。 不过,易失性 版本的性能更差,因此,如果非易失性 f 可用,则永远不要在重载决策中选择该版本。

请注意,代码实际上从不依赖于易失性内存访问。

Besides using it as intended, volatile is used in (template) metaprogramming. It can be used to prevent accidental overloading, as the volatile attribute (like const) takes part in overload resolution.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

This is legal; both overloads are potentially callable and do almost the same. The cast in the volatile overload is legal as we know bar won't pass a non-volatile T anyway. The volatile version is strictly worse, though, so never chosen in overload resolution if the non-volatile f is available.

Note that the code never actually depends on volatile memory access.

寂寞清仓 2024-07-12 01:02:32

我在 20 世纪 90 年代初开发的一个大型应用程序包含使用 setjmp 和 longjmp 的基于 C 的异常处理。 对于需要将值保留在充当“catch”子句的代码块中的变量,必须使用 volatile 关键字,以免这些变量存储在寄存器中并被 longjmp 擦除。

A large application that I used to work on in the early 1990s contained C-based exception handling using setjmp and longjmp. The volatile keyword was necessary on variables whose values needed to be preserved in the block of code that served as the "catch" clause, lest those vars be stored in registers and wiped out by the longjmp.

死开点丶别碍眼 2024-07-12 01:02:32

在标准 C 中,使用 易失性 的地方之一是信号处理程序。 事实上,在标准 C 中,您在信号处理程序中可以安全地执行的操作就是修改 易失性 sig_atomic_t 变量,或者快速退出。 事实上,据我所知,这是标准 C 中唯一需要使用 volatile 来避免未定义行为的地方。

ISO/IEC 9899:2011 §7.14.1.1 信号功能

¶5 如果信号不是由于调用 abortraise 函数而发生的,则
如果信号处理程序引用任何具有静态或线程的对象,则行为未定义
不是无锁原子对象的存储持续时间,除了将值分配给
声明为 volatile sig_atomic_t 的对象,或者信号处理程序调用任何函数
在标准库中,除了 abort 函数、_Exit 函数之外,
quick_exit 函数,或第一个参数等于的 signal 函数
与导致调用处理程序的信号相对应的信号号。
此外,如果对 signal 函数的此类调用导致返回 SIG_ERR,则
errno 的值不确定。252)

252) 如果异步信号处理程序生成任何信号,则行为未定义。

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

In Standard C, one of the places to use volatile is with a signal handler. In fact, in Standard C, all you can safely do in a signal handler is modify a volatile sig_atomic_t variable, or exit quickly. Indeed, AFAIK, it is the only place in Standard C that the use of volatile is required to avoid undefined behaviour.

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.

That means that in Standard C, you can write:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

and not much else.

POSIX is a lot more lenient about what you can do in a signal handler, but there are still limitations (and one of the limitations is that the Standard I/O library — printf() et al — cannot be used safely).

╭ゆ眷念 2024-07-12 01:02:32

实现无锁数据结构时必须使用 volatile。 否则,编译器可以自由地优化对变量的访问,这将改变语义。

换句话说,易失性告诉编译器对该变量的访问必须对应于物理内存读/写操作。

例如,InterlockedIncrement 在 Win32 API 中是这样声明的:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

You MUST use volatile when implementing lock-free data structures. Otherwise the compiler is free to optimize access to the variable, which will change the semantics.

To put it another way, volatile tells the compiler that accesses to this variable must correspond to a physical memory read/write operation.

For example, this is how InterlockedIncrement is declared in the Win32 API:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);
岁月流歌 2024-07-12 01:02:32

来自 Dan Saks 的一篇“不稳定的承诺”文章:

(...) 易失性对象是指其值可能会自发更改的对象。 也就是说,当您将一个对象声明为易失性时,您就告诉编译器该对象可能会更改状态,即使程序中似乎没有任何语句会更改它。”

以下是他关于 的三篇文章的链接易失性关键字:

From a "Volatile as a promise" article by Dan Saks:

(...) a volatile object is one whose value might change spontaneously. That is, when you declare an object to be volatile, you're telling the compiler that the object might change state even though no statements in the program appear to change it."

Here are links to three of his articles regarding the volatile keyword:

追我者格杀勿论 2024-07-12 01:02:32

如果您要从内存中的某个位置读取数据(例如,完全独立的进程/设备/任何可能写入的内容),则需要 易失性

我曾经在直接 C 语言的多处理器系统中使用双端口 RAM。我们使用硬件管理的 16 位值作为信号量来知道其他人何时完成。 本质上我们是这样做的:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

如果没有易失性,优化器会认为循环毫无用处(这家伙从来没有设置值!他疯了,摆脱那个代码!)并且我的代码将在没有获取信号量的情况下继续进行,导致以后出现问题。

volatile is needed if you are reading from a spot in memory that, say, a completely separate process/device/whatever may write to.

I used to work with dual-port ram in a multiprocessor system in straight C. We used a hardware managed 16 bit value as a semaphore to know when the other guy was done. Essentially we did this:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Without volatile, the optimizer sees the loop as useless (The guy never sets the value! He's nuts, get rid of that code!) and my code would proceed without having acquired the semaphore, causing problems later on.

我们只是彼此的过ke 2024-07-12 01:02:32

在开发嵌入式系统或设备驱动程序时,需要读取或写入内存映射的硬件设备,因此需要使用易失性。 特定设备寄存器的内容可能随时更改,因此您需要 volatile 关键字来确保此类访问不会被编译器优化掉。

volatile is needed when developing embedded systems or device drivers, where you need to read or write a memory-mapped hardware device. The contents of a particular device register could change at any time, so you need the volatile keyword to ensure that such accesses aren't optimised away by the compiler.

深白境迁sunset 2024-07-12 01:02:32

一些处理器具有超过 64 位精度的浮点寄存器(例如,没有 SSE 的 32 位 x86,请参阅 Peter 的评论)。 这样,如果您对双精度数字运行多个运算,您实际上会得到比将每个中间结果截断为 64 位更高精度的答案。

这通常很好,但这意味着根据编译器分配寄存器和优化的方式,对完全相同的输入执行完全相同的操作会得到不同的结果。 如果需要一致性,则可以使用 volatile 关键字强制每个操作返回内存。

它对于一些没有代数意义但减少浮点误差的算法也很有用,例如卡汉求和。 从代数上来说,它是一个 nop,因此除非某些中间变量是易失性的,否则它通常会被错误地优化。

Some processors have floating point registers that have more than 64 bits of precision (eg. 32-bit x86 without SSE, see Peter's comment). That way, if you run several operations on double-precision numbers, you actually get a higher-precision answer than if you were to truncate each intermediate result to 64 bits.

This is usually great, but it means that depending on how the compiler assigned registers and did optimizations you'll have different results for the exact same operations on the exact same inputs. If you need consistency then you can force each operation to go back to memory by using the volatile keyword.

It's also useful for some algorithms that make no algebraic sense but reduce floating point error, such as Kahan summation. Algebraicly it's a nop, so it will often get incorrectly optimized out unless some intermediate variables are volatile.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文