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

理解 C 中的“volatile”限定符

volatile 关键字旨在防止编译器对可能以编译器无法确定的方式更改的对象应用任何优化。 

声明为 volatile 的对象在优化中被忽略,因为它们的值可以随时被当前代码范围之外的代码更改。系统总是从内存位置读取易失性对象的当前值,而不是在请求时将其值保存在临时寄存器中,即使先前的指令要求来自同一对象的值也是如此。所以简单的问题是,变量的值如何以编译器无法预测的方式变化。考虑以下案例来回答这个问题。 

1)被范围外的中断服务程序修改的全局变量:例如,一个全局变量可以代表一个数据端口(通常是全局指针,称为内存映射IO),它将被动态更新。代码读取数据端口必须声明为易失性,以便获取端口上可用的最新数据。未能将变量声明为易失性,编译器将优化代码,使其仅读取一次端口并在临时寄存器中继续使用相同的值来加速程序(速度优化)。通常,当由于新数据的可用性而出现中断时,ISR 用于更新这些数据端口 

2)多线程应用程序中的全局变量:线程通信有多种方式,即消息传递、共享内存、邮箱等。全局变量是共享内存的弱形式。当两个线程通过全局变量共享信息时,需要用 volatile 进行限定。由于线程是异步运行的,因此一个线程对全局变量的任何更新都应该由另一个消费者线程重新获取。编译器可以读取全局变量并将它们放在当前线程上下文的临时变量中。为了消除编译器优化的影响,这些全局变量需要被限定为 volatile

如果我们不使用 volatile 限定符,可能会出现以下问题 
1) 打开优化时代码可能无法按预期工作。 
2) 启用和使用中断时,代码可能无法按预期工作。

让我们看一个例子来了解编译器如何解释 volatile 关键字。考虑下面的代码,我们正在使用指针更改 const 对象的值,并且我们正在编译没有优化选项的代码。因此编译器不会做任何优化并且会改变 const 对象的值。

/* Compile code without optimization option */
#include <stdio.h>
int main(void)
{
const int local = 10;
int *ptr = (int*) &local;

printf("Initial value of local : %d \n", local);

*ptr = 100;

printf("Modified value of local: %d \n", local);

return 0;
}

当我们使用 gcc 的“–save-temps”选项编译代码时,它会生成 3 个输出文件
1)预处理代码(具有 .i 扩展名) 
2)汇编代码(具有 .s 扩展名)和 
3)目标代码(具有 .o 选项) . 

我们编译代码没有优化,这就是为什么汇编代码的大小会更大(下面用红色突出显示)。

输出: 

  [narendra@ubuntu]$ gcc volatile.c -o volatile –save-temps
  [narendra@ubuntu]$ ./volatile
  Initial value of local : 10
  Modified value of local: 100
  [narendra@ubuntu]$ ls -l volatile.s
  -rw-r–r– 1 narendra narendra 731 2016-11-19 16:19 volatile.s
  [narendra@ubuntu]$

让我们用优化选项(即 -O 选项)编译相同的代码。在下面的代码中,“local”被声明为 const(和非易失性),GCC 编译器进行优化并忽略尝试更改 const 对象值的指令。因此 const 对象的值保持不变。 

/* Compile code with optimization option */
#include <stdio.h>

int main(void)
{
const int local = 10;
int *ptr = (int*) &local;

printf("Initial value of local : %d \n", local);

*ptr = 100;

printf("Modified value of local: %d \n", local);

return 0;
}

对于上面的代码,编译器做了优化,这就是为什么汇编代码的大小会减少。

输出: 

 [narendra@ubuntu]$ gcc -O3 volatile.c -o volatile –save-temps
  [narendra@ubuntu]$ ./volatile
  Initial value of local : 10
  Modified value of local: 10
  [narendra@ubuntu]$ ls -l volatile.s
  -rw-r–r– 1 narendra narendra 626 2016-11-19 16:21 volatile.s

让我们将 const 对象声明为 volatile 并使用优化选项编译代码。尽管我们使用优化选项编译代码,但 const 对象的值会发生变化,因为变量被声明为 volatile,这意味着不进行任何优化。 


/* Compile code with optimization option */
#include <stdio.h>

int main(void)
{
const volatile int local = 10;
int *ptr = (int*) &local;

printf("Initial value of local : %d \n", local);

*ptr = 100;

printf("Modified value of local: %d \n", local);

return 0;
}

输出: 

  [narendra@ubuntu]$ gcc -O3 volatile.c -o volatile –save-temp
  [narendra@ubuntu]$ ./volatile
  Initial value of local : 10
  Modified value of local: 100
  [narendra@ubuntu]$ ls -l volatile.s
  -rw-r–r– 1 narendra narendra 711 2016-11-19 16:22 volatile.s
  [narendra@ubuntu]$

上面的例子可能不是一个很好的实际例子,目的是解释编译器如何解释 volatile 关键字。作为一个实际的例子,想想手机上的触摸传感器。驱动抽象触摸传感器将读取触摸位置并将其发送到更高级别的应用程序。驱动程序本身不应修改(const-ness)读取位置,并确保它每次都读取触摸输入新鲜(易失性)。此类驱动程序必须以 const volatile 方式读取触摸传感器输入。

注意:以上代码是特定于编译器的,可能不适用于所有编译器。这些例子的目的是让读者理解这个概念。

 

 

这篇文章对您有用吗?