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


当前回答

volatile的另一个用途是信号处理程序。如果你有这样的代码:

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

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

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

其他回答

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

在我看来,你不应该对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告诉编译器不要优化与Volatile变量有关的任何东西。

至少有三个常见的原因使用它,所有的情况下,变量的值可以改变,而不需要从可见代码的操作:

当您与改变值本身的硬件进行交互时 当另一个线程运行时也使用了该变量 当有一个可能改变变量值的信号处理程序时。

假设你有一小块硬件被映射到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;
}

看起来很简单,但可能会失败,因为编译器可以随意更改数据和命令的写入顺序。这将导致我们的小工具使用之前的数据值发出命令。还可以看看busy循环中的wait。这个会被优化掉。编译器会尽量聪明,只读取一次isBusy的值,然后进入一个无限循环。这不是你想要的。

解决这个问题的方法是将指针gadget声明为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;
}

它有两个用途。这些在嵌入式开发中特别常用。

编译器不会优化使用volatile关键字定义的变量的函数 Volatile用于访问RAM、ROM等中的精确内存位置。这通常用于控制内存映射设备,访问CPU寄存器和定位特定的内存位置。

参见程序集清单示例。 回复:在嵌入式开发中使用C“volatile”关键字

我的简单解释是:

在某些情况下,基于逻辑或代码,编译器会对它认为不会改变的变量进行优化。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循环中它将永远为0。优化后,编译器会一直将其视为while(true),导致无限循环。

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