在 C 中正确使用 volatile 关键字

Proper use of volatile keyword in C

我是 C 的初学者,我遇到了以下代码:

#include "stdio.h"

unsigned int ReturnSquare(void);

int main(void) {

int k;
int *mPtr;

mPtr = (int*) 0x1234;

*mPtr = 10;

 k = (int) ReturnSquare();

 printf("%p --> %d\n",mPtr,k);

}

unsigned int ReturnSquare(void)
{
  unsigned  volatile int a = * (unsigned volatile int *) 0x1234;
  unsigned  volatile int b = * (unsigned volatile int *) 0x1234;
  return a * b;
}

你能帮我理解这段代码中 volatile 的用途吗?

看来程序运行不正常。非常欢迎任何建议和解释。提前谢谢你。

当您两次读取同一个寄存器时,编译器可以决定优化行为。

它可以把代码变成这样:

unsigned  int a = * (unsigned int *) 0x1234;
unsigned  int b = a;

当您添加 volatile 时,编译器不会在第二次读取时假设值相同,并且会生成额外的指令以取消引用指针以再次注册。

它现在对您来说可能太高级了,但是您可以使用编译器上的汇编输出选项来检查它,volatile 版本将有更多的汇编指令。

它强制编译器在每次使用时读取引用的值。编译器知道这个对象可以被编译器在正常程序执行路径中看不到的东西改变。

您显示的代码是 volatile 所做的错误示例,并且通常是 C 代码的错误示例。

首先,代码是这样做的:

mPtr = (int*) 0x1234;
*mPtr = 10;

它采用看似任意的地址 0x1234,并在其中放置一个 int 值。通常,您无法知道您被允许写入该地址。它可能没有映射到您的虚拟地址 space,如果是,那可能有一些重要的东西,覆盖它会破坏程序。所以这个程序在做一些不好的事情并且不受支持,我们不应该期望它会起作用。 (在特殊环境下,可能会指定内存地址space的布局,可以这样使用。这种情况应该始终清楚地记录下来,并且代码仅限于它设计的特定系统因为;它不适合用作通用 C 代码。)

其次,代码没有做任何特别的事情来显示有和没有 volatile 的对象之间的任何区别。除了使用 int 写入 0x1234 并使用 unsigned int 从中读取的错误之外,正常执行此代码将产生一个不足为奇的结果,即 100,如果程序没有由于使用 0x1234 而崩溃。一个更好的例子是这样的程序:

#include <stdio.h>

int main(void)
{
    int a = 1234;
    volatile int b = 5678;
    printf("Press enter to proceed.\n");
    getchar();
    printf("a = %d.\n", a);
    printf("b = %d.\n", b);
}

然后指导学生在启用优化和调试的情况下编译这个程序,运行它在调试器中,在程序等待输入时中断它(在调试器中),使用调试器来改变ab 的值,然后继续 运行ning 程序。结果将是程序显示 a 及其原始值 1234,但显示 b 及其更改后的值。 (实际上,由于优化,a 可能不会以调试器可以更改的方式存在。)

这将证明编译器假设它可以完全控制非易失性对象,例如 a,因此它可以以假设它们不会意外更改的方式优化代码,但编译器不会volatile 对象的此类假设。对于 volatile 对象,编译器每次在源代码中使用它时都会从内存中重新加载它(并且每次在源代码中修改它时都会将其写入内存)。

volatile 的意思是一个对象可能以编译器通常不知道的方式改变。因此,演示 volatile 的工作原理需要从程序外部修改程序。尽管调试器是实现此目的的一种方法,但 volatile 的预期用途是访问地址 space 中连接到 I/O 设备而不是普通内存的位置。当某些 input/output 操作发生时,这些位置可能会改变。 volatile 关键字告诉编译器不要将对象视为普通内存,以预期它们可能会因外部操作而意外更改。