为什么C中需要挥发性?它的用途是什么?它会做什么?
当前回答
在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".
其他回答
在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".
我的简单解释是:
在某些情况下,基于逻辑或代码,编译器会对它认为不会改变的变量进行优化。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的用例。
Volatile意味着存储可能在任何时候被改变,而且是在用户程序控制之外被改变。这意味着如果你引用变量,程序应该总是检查物理地址(即映射的输入fifo),而不是以缓存的方式使用它。
在我看来,你不应该对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;
}