为什么 C 中需要 volatile?

发布于 2024-07-07 21:36:10 字数 51 浏览 6 评论 0原文

为什么 C 中需要易失性? 它是干什么用的? 它会做什么?

Why is volatile needed in C? What is it used for? What will it do?

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

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

发布评论

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

评论(19

撩动你心 2024-07-14 21:36:10

易失性告诉编译器不要优化与易失性变量有关的任何内容。

使用它至少有三个常见原因,所有这些都涉及变量的值无需可见代码的操作即可更改

  • 情况
  • :有一个信号处理程序可能会更改变量的值。

假设您有一小块硬件映射到 RAM 的某个位置,并且有两个地址:一个命令端口和一个数据端口:

typedef struct
{
  int command;
  int data;
  int isBusy;
} MyHardwareGadget;

现在您想要发送一些命令:

void SendCommand(MyHardwareGadget* gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isBusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

看起来很简单,但它可能会失败,因为编译器可以自由地更改写入数据和命令的顺序。 这将导致我们的小工具使用先前的数据值发出命令。 另请查看 wait while busy 循环。 那个将被优化掉。 编译器会尝试变得聪明,只读取一次 isBusy 的值,然后进入无限循环。 那不是你想要的。

解决这个问题的方法是将指针gadget声明为易失性。 这样编译器就被迫执行您编写的操作。 它不能删除内存分配,不能在寄存器中缓存变量,也不能更改分配的顺序

这是正确的版本:

void SendCommand(volatile MyHardwareGadget* gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isBusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

volatile tells the compiler not to optimize anything that has to do with the volatile variable.

There are at least three common reasons to use it, all involving situations where the value of the variable can change without action from the visible code:

  • When you interface with hardware that changes the value itself
  • when there's another thread running that also uses the variable
  • when there's a signal handler that might change the value of the variable.

Let's say you have a little piece of hardware that is mapped into RAM somewhere and that has two addresses: a command port and a data port:

typedef struct
{
  int command;
  int data;
  int isBusy;
} MyHardwareGadget;

Now you want to send some command:

void SendCommand(MyHardwareGadget* gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isBusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

Looks easy, but it can fail because the compiler is free to change the order in which data and commands are written. This would cause our little gadget to issue commands with the previous data-value. Also take a look at the wait while busy loop. That one will be optimized out. The compiler will try to be clever, read the value of isBusy just once and then go into an infinite loop. That's not what you want.

The way to get around this is to declare the pointer gadget as volatile. This way the compiler is forced to do what you wrote. It can't remove the memory assignments, it can't cache variables in registers and it can't change the order of assignments either

This is the correct version:

void SendCommand(volatile MyHardwareGadget* gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isBusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}
回忆追雨的时光 2024-07-14 21:36:10

C中的volatile实际上是为了不自动缓存变量的值而存在的。 它会告诉编译器不要缓存该变量的值。 因此,每次遇到给定的易失性变量时,它都会生成代码从主内存中获取该变量的值。 使用此机制是因为操作系统或任何中断都可以随时修改该值。 因此,使用易失性将帮助我们每次都重新访问该值。

volatile in C actually came into existence for the purpose of not caching the values of the variable automatically. It will tell the compiler not to cache the value of this variable. So it will generate code to take the value of the given volatile variable from the main memory every time it encounters it. This mechanism is used because at any time the value can be modified by the OS or any interrupt. So using volatile will help us accessing the value afresh every time.

山人契 2024-07-14 21:36:10

易失性的另一个用途是信号处理程序。 如果您有这样的代码:

int quit = 0;
while (!quit)
{
    /* very small loop which is completely visible to the compiler */
}

编译器可以注意到循环体没有触及 quit 变量,并将循环转换为 while (true) 循环。 即使在 SIGINTSIGTERM 的信号处理程序上设置了 quit 变量; 编译器无法知道这一点。

但是,如果 quit 变量被声明为 volatile,则编译器每次都会被迫加载它,因为它可以在其他地方修改。 在这种情况下,这正是您想要的。

Another use for volatile is signal handlers. If you have code like this:

int quit = 0;
while (!quit)
{
    /* very small loop which is completely visible to the compiler */
}

The compiler is allowed to notice the loop body does not touch the quit variable and convert the loop to a while (true) loop. Even if the quit variable is set on the signal handler for SIGINT and SIGTERM; the compiler has no way to know that.

However, if the quit variable is declared volatile, the compiler is forced to load it every time, because it can be modified elsewhere. This is exactly what you want in this situation.

吻风 2024-07-14 21:36:10

易失性告诉编译器您的变量可能会通过其他方式更改,而不是访问它的代码。 例如,它可以是 I/O 映射的内存位置。 如果在这种情况下没有指定,则可以优化某些变量访问,例如,其内容可以保存在寄存器中,并且不会再次读回存储器位置。

volatile tells the compiler that your variable may be changed by other means, than the code that is accessing it. e.g., it may be a I/O-mapped memory location. If this is not specified in such cases, some variable accesses can be optimised, e.g., its contents can be held in a register, and the memory location not read back in again.

旧时浪漫 2024-07-14 21:36:10

请参阅 Andrei Alexandrescu 撰写的这篇文章,“易失性 - 多线程程序员的最好朋友

易失性关键字是
旨在防止编译器
可能呈现代码的优化
在某些情况下不正确
异步事件。 例如,如果
你将一个原始变量声明为
易失性,编译器不是
允许将其缓存在寄存器中——
一个常见的优化是
如果该变量是灾难性的
在多个线程之间共享。 所以
一般规则是,如果你有变量
必须共享的原始类型
在多个线程中,声明那些
变量易失性。 但是你可以
实际上可以用这个做更多的事情
关键字:你可以用它来捕获代码
那不是线程安全的,你可以
在编译时这样做。 本文
展示它是如何完成的; 解决方案
涉及一个简单的智能指针
也使得序列化变得容易
代码的关键部分。

本文适用于 C 和 C++。

另请参阅 Scott Meyers 的文章“C++ 和双重检查锁定的危险”和安德烈·亚历山德雷斯库:

因此,在处理某些内存位置(例如内存映射端口或 ISR [中断服务例程]引用的内存)时,必须暂停某些优化。 易失性的存在是为了指定对此类位置的特殊处理,具体来说:(1)易失性变量的内容是“不稳定的”(可以通过编译器未知的方式更改),(2)对易失性数据的所有写入都是“可观察的”,因此它们必须严格执行,(3)所有对易失性数据的操作都按照它们在源代码中出现的顺序执行。 前两条规则确保正确的阅读和写作。 最后一种允许实现混合输入和输出的 I/O 协议。 这就是 C 和 C++ 的 volatile 的非正式保证。

See this article by Andrei Alexandrescu, "volatile - Multithreaded Programmer's Best Friend"

The volatile keyword was
devised to prevent compiler
optimizations that might render code
incorrect in the presence of certain
asynchronous events. For example, if
you declare a primitive variable as
volatile, the compiler is not
permitted to cache it in a register --
a common optimization that would be
disastrous if that variable were
shared among multiple threads. So the
general rule is, if you have variables
of primitive type that must be shared
among multiple threads, declare those
variables volatile. But you can
actually do a lot more with this
keyword: you can use it to catch code
that is not thread safe, and you can
do so at compile time. This article
shows how it is done; the solution
involves a simple smart pointer that
also makes it easy to serialize
critical sections of code.

The article applies to both C and C++.

Also see the article "C++ and the Perils of Double-Checked Locking" by Scott Meyers and Andrei Alexandrescu:

So when dealing with some memory locations (e.g. memory mapped ports or memory referenced by ISRs [ Interrupt Service Routines ] ), some optimizations must be suspended. volatile exists for specifying special treatment for such locations, specifically: (1) the content of a volatile variable is "unstable" (can change by means unknown to the compiler), (2) all writes to volatile data are "observable" so they must be executed religiously, and (3) all operations on volatile data are executed in the sequence in which they appear in the source code. The first two rules ensure proper reading and writing. The last one allows implementation of I/O protocols that mix input and output. This is informally what C and C++'s volatile guarantees.

总以为 2024-07-14 21:36:10

我简单的解释是:

在某些场景下,编译器会根据逻辑或代码,对它认为不会改变的变量进行优化。 volatile 关键字可防止变量被优化。

例如:

bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
    // execute logic for the scenario where the USB isn't connected 
}

从上面的代码来看,编译器可能认为usb_interface_flag被定义为0,并且在while循环中它将永远为零。 优化后,编译器会一直将其视为while(true),从而导致无限循环。

为了避免这种情况,我们将标志声明为易失性,我们告诉编译器该值可能会被外部接口或程序的其他模块更改,即请不要优化它。 这就是 volatile 的用例。

My simple explanation is:

In some scenarios, based on the logic or code, the compiler will do optimisation of variables which it thinks do not change. The volatile keyword prevents a variable being optimised.

For example:

bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
    // execute logic for the scenario where the USB isn't connected 
}

From the above code, the compiler may think usb_interface_flag is defined as 0, and that in the while loop it will be zero forever. After optimisation, the compiler will treat it as while(true) all the time, resulting in an infinite loop.

To avoid these kinds of scenarios, we declare the flag as volatile, we are telling to compiler that this value may be changed by an external interface or other module of program, i.e., please don't optimise it. That's the use case for volatile.

森林散布 2024-07-14 21:36:10

挥发性的边际用途如下。 假设您要计算函数 f 的数值导数:

double der_f(double x)
{
    static const double h = 1e-3;
    return (f(x + h) - f(x)) / h;
}

问题是由于舍入,x+hx 通常不等于 h错误。 想一想:当你减去非常接近的数字时,你会丢失很多有效数字,这可能会破坏导数的计算(想想 1.00001 - 1)。 一种可能的解决方法是

double der_f2(double x)
{
    static const double h = 1e-3;
    double hh = x + h - x;
    return (f(x + hh) - f(x)) / hh;
}

,但根据您的平台和编译器开关,该函数的第二行可能会被积极优化的编译器删除。 因此,您改为

    volatile double hh = x + h;
    hh -= x;

强制编译器读取包含 hh 的内存位置,从而放弃最终的优化机会。

A marginal use for volatile is the following. Say you want to compute the numerical derivative of a function f :

double der_f(double x)
{
    static const double h = 1e-3;
    return (f(x + h) - f(x)) / h;
}

The problem is that x+h-x is generally not equal to h due to roundoff errors. Think about it : when you substract very close numbers, you lose a lot of significant digits which can ruin the computation of the derivative (think 1.00001 - 1). A possible workaround could be

double der_f2(double x)
{
    static const double h = 1e-3;
    double hh = x + h - x;
    return (f(x + hh) - f(x)) / hh;
}

but depending on your platform and compiler switches, the second line of that function may be wiped out by a aggressively optimizing compiler. So you write instead

    volatile double hh = x + h;
    hh -= x;

to force the compiler to read the memory location containing hh, forfeiting an eventual optimization opportunity.

倾城月光淡如水﹏ 2024-07-14 21:36:10

我会提到另一个场景,其中挥发物很重要。

假设您对一个文件进行内存映射以实现更快的 I/O,并且该文件可以在幕后更改(例如,该文件不在您的本地硬盘驱动器上,而是由另一台计算机通过网络提供服务)。

如果您通过指向非易失性对象的指针(在源代码级别)访问内存映射文件的数据,则编译器生成的代码可以在您不知情的情况下多次获取相同的数据。

如果该数据碰巧发生更改,您的程序可能会使用两个或多个不同版本的数据并进入不一致的状态。 如果程序处理不受信任的文件或来自不受信任位置的文件,这不仅会导致程序在逻辑上不正确的行为,还会导致程序中存在可利用的安全漏洞。

如果您关心安全性(而且您应该关心安全性),那么这是一个需要考虑的重要场景。

I'll mention another scenario where volatiles are important.

Suppose you memory-map a file for faster I/O and that file can change behind the scenes (e.g. the file is not on your local hard drive, but is instead served over the network by another computer).

If you access the memory-mapped file's data through pointers to non-volatile objects (at the source code level), then the code generated by the compiler can fetch the same data multiple times without you being aware of it.

If that data happens to change, your program may become using two or more different versions of the data and get into an inconsistent state. This can lead not only to logically incorrect behavior of the program but also to exploitable security holes in it if it processes untrusted files or files from untrusted locations.

If you care about security, and you should, this is an important scenario to consider.

颜漓半夏 2024-07-14 21:36:10

有两种用途。 这些特别是在嵌入式开发中用得比较多。

  1. 编译器不会优化使用 volatile 关键字定义的变量的函数

  2. Volatile 用于访问 RAM、ROM 等中的精确内存位置...这更常用于控制内存映射设备、访问 CPU 寄存器并定位特定内存位置。

请参阅带有程序集列表的示例。
回复:嵌入式开发中 C“易失性”关键字的使用

There are two uses. These are specially used more often in embedded development.

  1. The compiler will not optimise the functions that use variables that are defined with the volatile keyword

  2. Volatile is used to access exact memory locations in RAM, ROM, etc... This is used more often to control memory-mapped devices, access CPU registers and locate specific memory locations.

See examples with assembly listing.
Re: Usage of C "volatile" Keyword in Embedded Development

戴着白色围巾的女孩 2024-07-14 21:36:10

当您想要强制编译器不优化特定代码序列(例如,编写微基准)时,易失性也很有用。

Volatile is also useful, when you want to force the compiler not to optimize a specific code sequence (e.g. for writing a micro-benchmark).

可是我不能没有你 2024-07-14 21:36:10

在我看来,您不应该对 volatile 抱有太多期望。 为了进行说明,请查看Nils Pipenbrinck 的高票答案中的示例。

我想说,他的例子不适合易失性易失性仅用于:
阻止通常有用且理想的编译器优化。 它与线程安全、原子访问甚至内存顺序无关。

在该示例中:

    void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // Wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // Do nothing here.
      }

      // Set data first:
      gadget->data    = data;

      // Writing the command starts the action:
      gadget->command = command;
    }

gadget->command = command 之前的 gadget->data = data 仅在编译器编译的代码中得到保证。

在运行时,处理器仍然可以重新排序数据和命令分配,具体取决于处理器架构。 硬件可能会获取错误的数据(假设小工具映射到硬件 I/O)。 数据和命令分配之间需要内存屏障。

In my opinion, you should not expect too much from volatile. To illustrate, look at the example in Nils Pipenbrinck's highly-voted answer.

I would say, his example is not suitable for volatile. volatile is only used to:
prevent compiler optimizations that would normally be useful and desirable. It is nothing about thread safety, atomic access or even memory order.

In that example:

    void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // Wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // Do nothing here.
      }

      // Set data first:
      gadget->data    = data;

      // Writing the command starts the action:
      gadget->command = command;
    }

The gadget->data = data before gadget->command = command only is only guaranteed in the compiled code by the compiler.

At run time, the processor may still reorder the data and command assignment, depending on the processor architecture. The hardware could get the wrong data (suppose a gadget is mapped to hardware I/O). A memory barrier is needed between data and command assignment.

落在眉间の轻吻 2024-07-14 21:36:10

在 Dennis Ritchie 设计的语言中,对任何对象(地址尚未获取的自动对象除外)的每次访问都会表现为计算对象的地址,然后在该地址读取或写入存储。 这使得该语言非常强大,但优化机会受到严重限制。

虽然可以禁止编译器假设可寻址对象永远不会以奇怪的方式更改,但这种假设对于 C 程序中的绝大多数对象来说是适当且有用的,并且添加一个适合这种假设的所有对象的限定符。 另一方面,某些程序需要使用某些对象,但这种假设不成立。 为了解决这个问题,该标准规定编译器可能会假设未声明为 volatile 的对象的值不会以超出编译器控制的方式被观察或更改,或者超出合理的编译器的控制范围。理解。

由于不同的平台可能有不同的方式来在编译器的控制之外观察或修改对象,因此这些平台的高质量编译器在对易失性语义的精确处理上应该有所不同,这是适当的。 不幸的是,因为该标准未能建议用于任何特定平台上低级编程的高质量编译器应该以一种能够识别特定读/写操作的任何和所有相关影响的方式处理易失性。在该平台上,许多编译器都未能做到这一点,这使得以高效但不能被编译器“优化”破坏的方式处理后台 I/O 之类的事情变得更加困难。

In the language designed by Dennis Ritchie, every access to any object, other than automatic objects whose address had not been taken, would behave as though it computed the address of the object and then read or wrote the storage at that address. This made the language very powerful, but severely limited optimization opportunities.

While it might have been possible to forbid compilers from assuming that addressable objects would never be changed in weird ways, such an assumption would have been appropriate and useful for the vast majority of objects in C programs, and it would have been impractical to add a qualifier to all the objects for which such assumption would be appropriate. On the other hand, some programs need to use some objects for which such an assumption would not hold. To resolve this issue, the Standard says that compilers may assume that objects which are not declared volatile will not have their value observed or changed in ways that are outside the compiler's control, or would be outside a reasonable compiler's understanding.

Because various platforms may have different ways in which objects could be observed or modified outside a compiler's control, it is appropriate that quality compilers for those platforms should differ in their exact handling of volatile semantics. Unfortunately, because the Standard failed to suggest that quality compilers intended for low-level programming on any particular platform should handle volatile in a way that will recognize any and all relevant effects of a particular read/write operation on that platform, many compilers fall short of doing so in ways that make it harder to process things like background I/O in a way which is efficient but can't be broken by compiler "optimizations".

×眷恋的温暖 2024-07-14 21:36:10

易失性表示存储空间可能随时发生变化,并且被用户程序控制之外的事物所改变。

这意味着,如果您引用该变量,程序应始终检查物理地址(即映射的输入FIFO),并且不以缓存方式使用它。

volatile means the storage is likely to change at any time and be changed by something outside the control of the user program.

This means that if you reference the variable, the program should always check the physical address (i.e., a mapped input FIFO), and not use it in a cached way.

划一舟意中人 2024-07-14 21:36:10

简单来说,它告诉编译器不要对特定变量进行任何优化。 映射到设备寄存器的变量由设备间接修改。 在这种情况下,必须使用 volatile。

In simple terms, it tells the compiler not to do any optimisation on a particular variable. Variables which are mapped to device register are modified indirectly by the device. In this case, volatile must be used.

南风几经秋 2024-07-14 21:36:10

所有内容:

维基百科描述了有关 易失性的 Linux 内核的文档还对易失性做了很好的说明:

Wikipedia says everything about volatile:

And the Linux kernel's documentation also makes a excellent note about volatile:

伴随着你 2024-07-14 21:36:10

可以从编译代码外部更改易失性变量(例如,程序可以将易失性变量映射到内存映射寄存器)。

编译器不会对处理易失性变量的代码应用某些优化。 例如,它不会将其加载到寄存器而不将其写入内存。 这在处理硬件寄存器时很重要。

A volatile can be changed from outside the compiled code (for example, a program may map a volatile variable to a memory mapped register).

The compiler won't apply certain optimizations to code that handles a volatile variable. For example, it won't load it into a register without writing it to memory. This is important when dealing with hardware registers.

天冷不及心凉 2024-07-14 21:36:10

易失性经常被误解为“禁用优化”、同步对变量的访问或生成内存栅栏。 没有一个是完全如此的。 它所做的只是禁用任何假设变量未从外部上下文更改的编译器优化。

根据 GCC

消除优化效果,

Volatile 做了什么要理解 易失性,我们可以简单地看一下 GCC 生成的代码。 在第一个示例中,我们将 volatile 变量与 argc 相乘并存储在 i 中。

volatile int i = 0;

int main(int argc, char **argv)
{
    i *= argc;
    return 0;
}

请参阅此处使用 O2 的组装:
https://godbolt.org/z/cf4KGjxvP

main:
        mov     eax, DWORD PTR i[rip]
        add     eax, 1
        mov     DWORD PTR i[rip], eax
        xor     eax, eax
        ret
i:
        .zero   4

Volatile 告诉编译器该变量不能被期望在编译时间在访问之间保持相同。 它可能在执行过程中随时发生变化。 编译器被告知禁用死存储消除,因为这样做可能会导致意外行为。

引用该变量而不对其执行任何操作也会生成负载。
https://godbolt.org/z/z9cffTsqb

基本上,编译器会禁用任何消除负载或商店。

这是栅栏吗? 关于海湾合作委员会,是的。

在 GCC 上,易失性似乎充当编译器栅栏。
这段代码:

volatile int i,j,k,l;

int main(int argc, char **argv)
{
    i += j + k + l;
}

当添加的顺序改变时,对应的程序集有不同的加载顺序。
参见: https://godbolt.org/z/z9KG3PnbE

所以在GCC上, volatile是编译器内存栅栏,但不会生成栅栏指令来序列化访问。

在 MSVC 上,使用相反的加载生成相同的样本,但更改变量顺序也会影响加载顺序。 MSVC 也将其视为编译器栅栏。
https://godbolt.org/z/snWz6fYa6

Volatile 不做什么

Volatile 不是内存栅栏。 编译器可以将其视为一个,但不能保证。 只要不消除内存访问,就可以自由地重新排序。 这意味着如果您确实需要在两个函数调用之间进行内存访问,则可能不会以这种方式生成。

相反,必须显式使用编译器或实内存栅栏。 除非用锁或栅栏进行控制,否则多个处理器之间的并发访问可能无法工作。

易失性不能保证原子性。 加载和存储将通过单个指令进行,但可能不使用原子操作。 在第一个示例中,通过单个操作增加数字。

结论

Volatile 对变量的行为提供的保证很少。 对于其非常狭窄的范围之外的任何情况,都应将其视为不可移植的。 易失性变量访问应封装在辅助函数内,并且应避免易失性函数参数或返回。

应使用 volatile 的情况包括:

  • 内存映射 IO 寄存器
  • 信号处理程序变量
  • 中断服务例程变量(例如,在循环中等待,直到设备发出“就绪”信号)
  • 测试单个变量优化的有效性

多线程编程绝不应包括易失性,因为获取变量的关键部分可确保在其中允许所有优化,因为没有其他东西可以访问该变量。

Volatile is often misunderstood as "disabling optimizations," synchronizing access to a variable, or generating memory fences. None are entirely the case. All it does is disable any compiler optimizations that assume the variable has not changed from an outside context.

What Volatile Does According to GCC

Eliminating Opimizations Effect

To understand volatile, we can simply look at the code generated by GCC. In the first example we will multiply the volatile variable with argc and store in i.

volatile int i = 0;

int main(int argc, char **argv)
{
    i *= argc;
    return 0;
}

See assembly here with O2:
https://godbolt.org/z/cf4KGjxvP

main:
        mov     eax, DWORD PTR i[rip]
        add     eax, 1
        mov     DWORD PTR i[rip], eax
        xor     eax, eax
        ret
i:
        .zero   4

Volatile tells the compiler that the variable cannot be expected at compile time to have remained the same between accesses. It could change at any point during execution. The compiler is told to disable dead store elimination since doing so could lead to unexpected behavior.

Referencing the variable without doing anything to it also generates a load.
https://godbolt.org/z/z9cffTsqb

Basically, the compiler disables any optimization that eliminates loads or stores.

Is It A Fence? On GCC, Yes.

On GCC, volatile appears to act as a compiler fence.
This code:

volatile int i,j,k,l;

int main(int argc, char **argv)
{
    i += j + k + l;
}

When the order of the addition is changed, the corresponding assembly has a different load order.
See: https://godbolt.org/z/z9KG3PnbE

So on GCC, volatile is a compiler memory fence, but no fence instructions are generated to serialize access.

On MSVC, the same sample was generated with the loads in reverse, but changing the variable order did also affect the load order. MSVC also regards it as a compiler fence.
https://godbolt.org/z/snWz6fYa6

What Volatile Does Not Do

Volatile is not a memory fence. The compiler could treat it as one, but there is no guarantee. It is free to reorder memory accesses as long as it does not eliminate them. That means if you really need a memory access to happen between two function calls, it may not be generated that way.

Instead, a compiler or real memory fence must be explicitly used. Concurrent access between multiple processors may not work unless controlled with a lock or fence.

Volatile does not ensure atomicity. The loads and stores will happen with single instructions, but atomic operations may not be used. In the first example, the number was incremented with a single operation.

Conclusion

Volatile provides very few guarantees about the behavior of the variable. It should be considered non-portable for any situation outside its very narrow scope. Volatile variable access should be encapsulated inside helper functions and volatile function parameters or returns should be avoided.

The situations where volatile should be used are:

  • Memory mapped IO registers
  • Signal handler variables
  • Interrupt service routine variables (for example, waiting in a loop until device gives "ready" signal)
  • Testing the effectiveness of optimization on a single variable

Multithreaded programming should never include volatile since acquiring a critical section for a variable ensures that within it all optimizations are allowed because nothing else can access the variable.

风吹过旳痕迹 2024-07-14 21:36:10

正如这里许多人正确建议的那样,易失性关键字的流行用途是跳过易失性变量的优化。

在阅读了有关 volatile 的内容后,我想到的、值得一提的最佳优点是——防止在出现 longjmp回滚变量。 非本地跳转。

这是什么意思?

它只是意味着在执行堆栈展开后将保留最后一个值,以返回到某个先前的堆栈帧; 通常是在某些错误情况下。

由于它超出了这个问题的范围,因此我不会在这里详细介绍 setjmp/longjmp ,但它值得一读; 以及如何使用波动性特征来保留最后的值。

As rightly suggested by many here, the volatile keyword's popular use is to skip the optimisation of the volatile variable.

The best advantage that comes to mind, and worth mentioning after reading about volatile is -- to prevent rolling back of the variable in case of a longjmp. A non-local jump.

What does this mean?

It simply means that the last value will be retained after you do stack unwinding, to return to some previous stack frame; typically in case of some erroneous scenario.

Since it'd be out of scope of this question, I am not going into details of setjmp/longjmp here, but it's worth reading about it; and how the volatility feature can be used to retain the last value.

¢蛋碎的人ぎ生 2024-07-14 21:36:10

它不允许编译器自动更改变量的值。 易失性变量供动态使用。

It does not allow the compiler to automatically change values of variables. A volatile variable is for dynamic use.

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