在C中正确使用volatile关键字

时间:2018-09-11 10:04:27

标签: c volatile

我是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的用途?

该程序似乎无法正常运行。任何建议和解释都非常欢迎。预先谢谢你。

2 个答案:

答案 0 :(得分:2)

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

它可以将代码变成这样:

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

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

现在可能对您来说太高级了,但是您可以使用编译器上的汇编输出选项进行检查,易失性版本将提供更多的汇编指令。

答案 1 :(得分:2)

您显示的代码是volatile的错误示例,而对于C代码而言,通常是错误的示例。

首先,然后代码执行此操作:

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

它使用一个看似任意的地址0x1234,并在其中放置一个int值。通常,您不知道您是否可以写入该地址。它可能未映射到您的虚拟地址空间中,如果是,则那里可能有重要的内容,并且对其进行写操作将破坏程序。因此,该程序正在做一些不好的事情且不受支持,我们不应该期望它会起作用。 (在特殊环境中,可能指定了内存地址空间的布局,并且可以按这种方式使用。这种情况应始终清楚地记录在案,并且代码仅限于针对其设计的特定系统;不适合用作通用C代码。)

第二,代码没有做任何特别的事情,以显示带有和不带有volatile的对象之间的任何区别。除了使用0x1234写入int并使用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);
}

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

这将表明编译器假定它对非易失性对象(例如a)具有完全控制权,因此它可以通过优化代码的方式来假定它们不会发生意外更改,但是编译器不会做出此类假设与a对象。借助volatile对象,编译器每次在源代码中使用它时都会从内存中重新加载它(并且每次在源代码中对其进行修改时,它都会将它写入内存中。)

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