简体   繁体   English

为什么 volatile 存在?

[英]Why does volatile exist?

What does the volatile keyword do? volatile关键字有什么作用? In C++ what problem does it solve?在C++中它解决了什么问题?

In my case, I have never knowingly needed it.就我而言,我从来没有故意需要它。

volatile is needed if you are reading from a spot in memory that, say, a completely separate process/device/whatever may write to.如果您从内存中的某个位置读取数据,例如一个完全独立的进程/设备/任何可能写入的内容,则需要volatile

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.我曾经在直接 C 的多处理器系统中使用双端口 ram。我们使用硬件管理的 16 位值作为信号量来了解其他人何时完成。 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.如果没有volatile ,优化器将循环视为无用(这家伙从不设置值!他疯了,摆脱那些代码!)并且我的代码将在没有获得信号量的情况下继续运行,从而导致以后出现问题。

volatile is needed when developing embedded systems or device drivers, where you need to read or write a memory-mapped hardware device.在开发嵌入式系统或设备驱动程序时需要volatile ,您需要读取或写入内存映射硬件设备。 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.特定设备寄存器的内容可能随时更改,因此您需要volatile关键字以确保此类访问不会被编译器优化掉。

Some processors have floating point registers that have more than 64 bits of precision (eg. 32-bit x86 without SSE, see Peter's comment).一些处理器具有超过 64 位精度的浮点寄存器(例如,没有 SSE 的 32 位 x86,请参阅 Peter 的评论)。 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.这样,如果对双精度数运行多个运算,实际上得到的结果比将每个中间结果截断为 64 位的结果精度更高。

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.如果您需要一致性,那么您可以使用 volatile 关键字强制每个操作返回内存。

It's also useful for some algorithms that make no algebraic sense but reduce floating point error, such as Kahan summation.它对于一些没有代数意义但减少浮点误差的算法也很有用,例如 Kahan 求和。 Algebraicly it's a nop, so it will often get incorrectly optimized out unless some intermediate variables are volatile.代数上它是一个 nop,所以它通常会被错误地优化掉,除非一些中间变量是 volatile。

From a "Volatile as a promise" article by Dan Saks:来自 Dan Saks 的“Volatile as a promise”文章:

(...) a volatile object is one whose value might change spontaneously. (...) 一个 volatile 对象是一个其值可能会自发改变的对象。 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."也就是说,当你将一个对象声明为 volatile 时,你是在告诉编译器该对象可能会改变状态,即使程序中的语句似乎没有改变它。”

Here are links to three of his articles regarding the volatile keyword:以下是他关于volatile关键字的三篇文章的链接:

You MUST use volatile when implementing lock-free data structures.在实现无锁数据结构时,您必须使用 volatile。 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.换句话说, volatile 告诉编译器对这个变量的访问必须对应于物理内存读/写操作。

For example, this is how InterlockedIncrement is declared in the Win32 API:例如,这是在 Win32 API 中声明 InterlockedIncrement 的方式:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

In Standard C, one of the places to use volatile is with a signal handler.在标准 C 中,使用volatile的地方之一是信号处理程序。 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.事实上,在标准 C 中,您可以在信号处理程序中安全地做的就是修改volatile sig_atomic_t变量,或者快速退出。 Indeed, AFAIK, it is the only place in Standard C that the use of volatile is required to avoid undefined behaviour.事实上,AFAIK,这是标准 C 中唯一需要使用volatile以避免未定义行为的地方。

ISO/IEC 9899:2011 §7.14.1.1 The signal function ISO/IEC 9899:2011 §7.14.1.1 signal函数

¶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. ¶5 如果信号不是作为调用abortraise函数的结果而发生的,如果信号处理程序引用任何具有静态或线程存储持续时间的对象,并且不是无锁原子对象,则该行为是未定义的,而不是通过赋值声明为volatile sig_atomic_t的对象的值,或者信号处理程序调用标准库中除abort函数、 _Exit函数、 quick_exit函数或第一个参数等于对应的信号编号的signal函数之外的任何函数导致调用处理程序的信号。 Furthermore, if such a call to the signal function results in a SIG_ERR return, the value of errno is indeterminate.此外,如果对signal函数的此类调用导致 SIG_ERR 返回,则errno的值是不确定的。 252) 252)

252) If any signal is generated by an asynchronous signal handler, the behavior is undefined. 252)如果异步信号处理程序生成任何信号,则行为未定义。

That means that in Standard C, you can write:这意味着在标准 C 中,您可以编写:

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). POSIX 对您在信号处理程序中可以做什么更加宽容,但仍然存在限制(其中一个限制是标准 I/O 库 — printf()等 — 无法安全使用)。

A large application that I used to work on in the early 1990s contained C-based exception handling using setjmp and longjmp.我在 1990 年代初期曾经开发过的一个大型应用程序包含使用 setjmp 和 longjmp 的基于 C 的异常处理。 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. volatile 关键字对于其值需要保留在充当“catch”子句的代码块中的变量是必需的,以免这些变量存储在寄存器中并被 longjmp 清除。

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.没有“易失性”,循环就变成了一个 noop——据编译器所知,变量永远不会改变,所以它优化了检查。

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

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

Besides using it as intended, volatile is used in (template) metaprogramming.除了按预期使用它外, volatile 还用于(模板)元编程中。 It can be used to prevent accidental overloading, as the volatile attribute (like const) takes part in overload resolution.它可用于防止意外重载,因为 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); }
};

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. volatile重载中的转换是合法的,因为我们知道 bar 无论如何都不会传递非 volatile T The volatile version is strictly worse, though, so never chosen in overload resolution if the non-volatile f is available.但是, volatile版本更糟糕,因此如果非 volatile f可用,则永远不要在重载决议中选择。

Note that the code never actually depends on volatile memory access.请注意,代码实际上从未依赖于volatile内存访问。

  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)曾帮助我克服编译器的错误(优化期间错误生成的代码)

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. volatile关键字旨在防止编译器对可能以编译器无法确定的方式更改的对象应用任何优化。

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.声明为volatile对象从优化中省略,因为它们的值可以随时被当前代码范围之外的代码更改。 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.系统总是从内存位置读取volatile对象的当前值,而不是在请求时将其值保存在临时寄存器中,即使先前的指令要求来自同一对象的值。

Consider the following cases考虑以下情况

1) Global variables modified by an interrupt service routine outside the scope. 1) 被范围外的中断服务程序修改的全局变量。

2) Global variables within a multi-threaded application. 2) 多线程应用程序中的全局变量。

If we do not use volatile qualifier, the following problems may arise如果我们不使用 volatile 限定符,可能会出现以下问题

1) Code may not work as expected when optimisation is turned on. 1) 打开优化后,代码可能无法按预期工作。

2) Code may not work as expected when interrupts are enabled and used. 2) 启用和使用中断时,代码可能无法按预期工作。

Volatile: A programmer's best friend Volatile:程序员最好的朋友

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

Your program seems to work even without volatile keyword?即使没有volatile关键字,您的程序似乎也能工作? Perhaps this is the reason:或许这就是原因:

As mentioned previously the volatile keyword helps for cases like如前所述, volatile关键字有助于以下情况

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.但是一旦调用外部或非内联函数,似乎几乎没有效果。 Eg:例如:

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

Then with or without volatile almost the same result is generated.然后有或没有volatile产生几乎相同的结果。

As long as g() can be completely inlined, the compiler can see everything that's going on and can therefore optimize.只要 g() 可以完全内联,编译器就可以看到正在发生的一切,因此可以进行优化。 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!但是要当心,当您的函数 g() 变为内联时(由于显式更改或由于编译器/链接器的聪明才智),如果您忘记了volatile关键字,您的代码可能会中断!

Therefore I recommend to add the volatile keyword even if your program seems to work without.因此我建议添加volatile关键字,即使您的程序似乎没有工作。 It makes the intention clearer and more robust in respect to future changes.对于未来的变化,它使意图更清晰、更稳健。

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.在 C 的早期,编译器会将所有读取和写入左值的操作解释为内存操作,以与代码中出现的读取和写入相同的顺序执行。 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.标准没有提供特别详细的控制,而是选择了一个简单的模型:如果一系列访问是使用不符合volatile限定的左值完成的,编译器可以按照它认为合适的方式重新排序和合并它们。 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.如果使用volatile限定的左值完成操作,则质量实现应提供针对其预期平台和应用程序领域的代码可能需要的任何额外排序保证,而不要求程序员使用非标准语法。

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.这使得volatile比它应该的有用得多。 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:例如,在 gcc 或 clang 上,需要实现基本“切换互斥锁”的程序员必须执行一个四件事:

  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.将互斥锁保护的所有对象限定为volatile如果所有访问都发生在获取互斥锁之后和释放它之前,则不需要这样做。

  3. Use optimization level 0 to force the compiler to generate code as though all objects that aren't qualified register are volatile .使用优化级别 0 强制编译器生成代码,就好像所有未限定register对象都是volatile

  4. Use gcc-specific directives.使用特定于 gcc 的指令。

By contrast, when using a higher-quality compiler which is more suitable for systems programming, such as icc, one would have another option:相比之下,当使用更适合系统编程的更高质量的编译器(例如 icc)时,人们会有另一种选择:

  1. Make sure that a volatile -qualified write gets performed everyplace an acquire or release is needed.确保在每个需要获取或释放的地方执行volatile限定的写入。

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.获取一个基本的“切换互斥锁”需要一个volatile读(看看它是否准备好),并且不应该也需要一个volatile写(另一方不会尝试重新获取它,直到它被交还)但是必须执行无意义的volatile写入仍然比 gcc 或 clang 下可用的任何选项更好。

All answers are excellent.所有的答案都很棒。 But on the top of that, I would like to share an example.但最重要的是,我想分享一个例子。

Below is a little cpp program:下面是一个小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;
}

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.您可以在程序集中看到,没有为sprintf生成程序集代码,因为编译器假定x不会在程序之外更改。 And same is the case with the while loop. while循环也是如此。 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) ).由于优化, while循环被完全删除,因为编译器认为它是无用的代码,因此直接将5分配给x (参见movl $5, x(%rip) )。

The problem occurs when what if an external process/ hardware would change the value of x somewhere between x = 8;如果外部进程/硬件会在x = 8;之间的某处更改x的值,则会出现问题x = 8; and if(x == 8) .if(x == 8) We would expect else block to work but unfortunately the compiler has trimmed out that part.我们希望else块能够工作,但不幸的是编译器已经删减了那部分。

Now, in order to solve this, in the assembly.cpp , let us change int x;现在,为了解决这个问题,在assembly.cpp ,让我们更改int x; to volatile int x;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.在这里您可以看到生成了sprintfprintfwhile循环的汇编代码。 The advantage is that if the x variable is changed by some external program or hardware, sprintf part of the code will be executed.好处是如果x变量被一些外部程序或硬件改变了, sprintf部分代码会被执行。 And similarly while loop can be used for busy waiting now.类似地, while循环现在可用于忙等待。

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

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.唯一的解决方案(虽然不是很正确)是将 'faulty' 变量声明为 volatile。

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

Other answers already mention avoiding some optimization in order to:其他答案已经提到避免一些优化,以便:

  • use memory mapped registers (or "MMIO")使用内存映射寄存器(或“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.当您需要一个值看起来来自外部并且是不可预测的,并且避免基于已知值的编译器优化,并且当结果未被实际使用但您需要计算它时,或者它被使用时,Volatile 是必不可少的您想为基准多次计算它,并且您需要计算在精确点开始和结束。

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 .易失性读取就像输入操作(如scanf或使用cin ):值似乎来自程序的外部,因此任何依赖于该值的计算都需要在它之后开始

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 .易失性写入就像输出操作(如printfcout的使用):该值似乎是在程序外部传达的,因此如果该值取决于计算,则需要在 之前完成

So a pair of volatile read/write can be used to tame benchmarks and make time measurement meaningful .因此,可以使用一对 volatile 读/写来驯服基准并使时间测量有意义

Without volatile, your computation could be started by the compiler before, as nothing would prevent reordering of computations with functions such as time measurement .如果没有 volatile,您的计算可以在之前由编译器启动,因为没有什么可以阻止使用诸如时间测量之类的函数重新排序计算

I would like to quote Herb Sutter's words from his GotW #95 , which can help to understand the meaning of the volatile variables:我想引用 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. C++ volatile变量(在C#Java等语言中没有类似物)总是超出了本文和任何其他关于内存模型和同步的文章的范围。 That's because C++ volatile variables aren't about threads or communication at all and don't interact with those things.那是因为C++ volatile变量根本与线程或通信无关,也不与这些东西交互。 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 (eg, written to by a daughter card), have more than one address, or is otherwise “strange” and beyond the language.相反, C++ volatile变量应该被视为进入语言之外的不同领域的门户——根据定义不遵守语言的内存模型的内存位置,因为该内存位置是由硬件访问的(例如,由子卡写入) ,拥有多个地址,或者“奇怪”且超出语言范围。 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变量通常是每个关于同步的指南的例外,因为使用普通工具(互斥锁、原子等)总是天生“活泼”和不可同步的,并且更普遍地存在于语言和编译器的所有正常之外,包括它们通常不能被编译器优化(因为编译器不允许知道它们的语义;一个volatile int vi;可能不像普通的int那样表现,你甚至不能假设像vi = 5; int read_back = vi;这样的代码vi = 5; int read_back = vi;保证会导致read_back == 5 ,或者像int i = vi; int j = vi;这样的代码读取 vi 两次将导致i == j ,如果vi是硬件计数器,则它不会为真)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM