我是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
所做的一个不好的例子,也是C代码的一个坏例子。
首先,然后代码执行此操作:
mPtr = (int*) 0x1234;
*mPtr = 10;
它需要一个看似随意的地址,0x1234
,并在那里放一个int
值。一般情况下,您无法知道您是否可以写入此地址。它可能没有映射到您的虚拟地址空间中,如果是,则可能存在重要的事情,并且在其上写入会破坏程序。所以这个程序做得不好而且不受支持,我们不应该期望它会起作用。 (在特殊环境中,可以指定存储器地址空间的布局,并且可以以这样的方式使用。这种情况应该始终清楚地记录,并且代码仅限于它为其设计的特定系统;它是不适合用作通用C代码。)
其次,代码没有什么特别之处,显示有和没有volatile
的对象之间的任何差异。除了使用0x1234
写入int
并使用unsigned int
读取它的错误之外,如果程序因使用0x1234
而没有崩溃,正常执行此代码将产生一个不足为奇的结果,100。一个更好的例子是这样的程序:
#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);
}
然后学生将被指示编译该程序并启用优化和调试,在调试器中运行它,在程序等待输入时中断它(在调试器中),使用调试器更改a
和b
的值,然后继续运行该程序。结果是程序显示a
及其原始值1234,但显示b
及其更改的值。 (事实上,由于优化,a
可能不会以调试器可以更改的方式存在。)
这将证明编译器假定它完全控制非易失性对象(如a
),因此它可以以假设它们不会意外更改的方式优化代码,但编译器不会使用volatile
对象进行此类假设。使用volatile
对象,编译器每次在源代码中使用时都会从内存中重新加载它(并且每次在源代码中修改它时都会将其写入内存)。
volatile
的含义是对象可能以编译器通常不知道的方式改变。因此,演示易失性如何工作需要从程序外部修改程序。尽管调试器是实现此目的的一种方法,但volatile
的预期用途是访问连接到I / O设备而非普通内存的地址空间中的位置。当某些输入/输出操作发生时,这些位置可能会改变。 volatile
关键字告诉编译器不要将对象视为普通内存,以期它们可能会从外部操作中意外更改。