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


当前回答

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

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

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

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

如果您关心安全性,这是一个需要考虑的重要场景。

其他回答

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

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

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