为什么C中需要挥发性?它的用途是什么?它会做什么?


当前回答

Volatile意味着存储可能在任何时候被改变,而且是在用户程序控制之外被改变。这意味着如果你引用变量,程序应该总是检查物理地址(即映射的输入fifo),而不是以缓存的方式使用它。

其他回答

volatile变量可以从编译代码的外部进行更改(例如,程序可以将volatile变量映射到内存映射寄存器)。编译器不会对处理易失性变量的代码应用某些优化——例如,它不会在不将其写入内存的情况下将其加载到寄存器。这在处理硬件寄存器时很重要。

volatile的边缘用法如下。假设你想计算一个函数f的数值导数:

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

问题是由于舍入误差,x+h-x通常不等于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的内存位置,从而丧失最终的优化机会。

在我看来,你不应该对volatile期望太高。为了说明这一点,看看尼尔斯·派彭布林克(Nils Pipenbrinck)的高票数回答中的例子。

我想说,他的例子并不适用于volatile。Volatile只用于: 阻止编译器进行有用和理想的优化。这与线程安全、原子访问甚至内存顺序无关。

在这个例子中:

    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->data = gadget->command = command之前的数据仅由编译器在编译后的代码中保证。在运行时,处理器仍然可能根据处理器架构对数据和命令分配进行重新排序。硬件可能会得到错误的数据(假设gadget映射到硬件I/O)。数据和命令分配之间需要内存屏障。

Volatile告诉编译器你的变量可以通过其他方式被改变,而不是通过访问它的代码。例如,它可能是一个I/ o映射的内存位置。如果在这种情况下没有指定这一点,一些变量访问可以被优化,例如,它的内容可以保存在寄存器中,并且内存位置不会再次读入。

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

While it might have been possible to add a qualifier that would invite a compiler to assume that a particular object wouldn't be changed in weird ways, such an assumption would be appropriate 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 a 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".