1. 主页
  2. 文档
  3. C语言教程
  4. C语言存储类
  5. 理解 C 中的“volatile”限定符

理解 C 中的“volatile”限定符

尽管有大量关于 C 语言的文献,但“ volatile ”关键字在某种程度上还是没有被很好地理解(即使是有经验的 C 程序员)。我们认为,主要原因是在用高级语言编写的典型 C 程序中没有“ volatile ”变量的实际用例。基本上,除非您在 C 中进行一些低级硬件编程,否则您可能不会使用被限定为“volatile ”的变量。通过低级编程,我们指的是一段 C 代码,它处理外围设备、IO 端口(主要是内存映射的 IO 端口)、与硬件交互的中断服务例程(ISR)。这就是为什么拥有一个可以轻松展示“volatile ”关键字。 
事实上,在这篇文章中,如果我们能解释一下 ‘ volatile的含义和目的‘,它将作为进一步研究和在 C 中使用 ‘volatile’ 的基本基础。要理解 ‘volatile’,我们首先需要了解编译器对 C 程序所做的事情的一些背景知识。在高层次上,我们知道编译器将 C 代码转换为机器代码,这样可执行文件就可以在没有实际源代码的情况下运行。与其他技术类似,编译器技术也有了很大的发展。在将源代码转换为机器代码时,编译器通常会尝试优化输出,以便最终需要执行较少的机器代码。一种这样的优化是删除不必要的机器代码来访问从编译器的角度来看没有改变的变量。假设我们有以下代码:

uint32 status = 0;

while (status == 0)
{
/*Let us assume that status isn't being changed
in this while loop or may be in our whole program*/

/*So long as status (which could be reflecting
status of some IO port) is ZERO, do something*/
}

优化编译器会看到状态没有被while循环改变。因此,无需在每次循环迭代后一次又一次地访问状态变量。因此编译器会将此循环转换为无限循环,即while (1),这样就不需要机器代码来读取状态。请注意,编译器不知道状态是一个特殊变量,它可以在任何时间点从当前程序外部更改,例如在外围设备上发生了一些 IO 操作,其设备 IO 端口被内存映射到该变量。所以实际上,我们希望编译器访问状态每次循环迭代后的变量,即使它没有被编译器编译的程序修改。
有人可能会争辩说,我们可以关闭此类程序的所有编译器优化,这样我们就不会遇到这种情况。由于多种原因,这不是一个选项,例如 
A)每个编译器实现都不同,因此它不是可移植的解决方案 
B)仅仅因为一个变量,我们不想关闭编译器在其他部分所做的所有其他优化我们的节目。 
C) 通过关闭所有优化,我们的低级程序无法按预期工作,例如大小增加过多或执行延迟。
这就是“易失性”出现的地方。基本上,我们需要指示 Compilerstatus是特殊变量,因此不允许对此变量进行此类优化。有了这个,我们将定义我们的变量如下:

volatile uint32 status = 0;

为了解释的简单,我们选择了上面的例子。但一般来说,volatile与指针一起使用,如下所示:

volatile uint32 * statusPtr = 0xF1230000

这里,statusPtr指向一个内存位置(例如,对于某个 IO 端口),在该位置,内容可以在任何时间点从某个外围设备发生变化。请注意,我们的程序可能无法控制或了解该内存何时会更改。所以我们将它设为“ volatile ”,这样编译器就不会对 statusPtr指向的volatile变量进行优化。在我们讨论“ volatile ”的上下文中,我们引用了 C 语言标准,即 ISO/IEC 9899 C11 – 第 6.7.3 条 “具有 volatile 限定类型的对象可以以实现未知的方式进行修改或具有其他未知方面影响。” 


“易失性声明可用于描述对应于内存映射输入/输出端口的对象或由异步中断函数访问的对象。除非评估表达式的规则允许,否则对如此声明的对象的操作不应被实现“优化”或重新排序。”
基本上,C 标准说“volatile ”变量可以从程序外部改变,这就是为什么编译器不应该优化它们的访问。现在,您可以猜测在您的程序中有太多的 ‘ volatile ‘ 变量也会导致更少和更少的编译器优化。我们希望它为您提供有关“易失性”的含义和目的的足够背景知识。

这篇文章对您有用吗?