相关问题已被问过很多次,但它们似乎都模糊地暗示了编译器可能进行的优化,因此我们需要使用
volatile
来避免所述优化。因此,我在这里有兴趣了解编译器会进行哪些优化,或者等效地,如果我没有在下面的代码片段中包含 volatile
,该代码片段会使用内存映射接口轮询一些键盘,那么会(或可能)出现什么问题,然后,一旦收到字符,将其发送到显示器:
/* Define register addresses. */
#define KBD_DATA (volatile char *) 0x4000
#define KBD_STATUS (volatile char *) 0x4004
#define DISP_DATA (volatile char *) 0x4008
#define DISP_STATUS (volatile char *) 0x4012
void main() {
char ch;
/* Transfer the characters. */
while (1) {
while ((*KBD_STATUS & 0x2) == 0);
ch = *KBD_DATA;
while ((*DISP_STATUS & 0x4) == 0);
*DISP_DATA = ch;
}
}
以上内容来自 Hamacher 等人的 Computer Organization。他们写下以下内容:
请注意,KBD_STATUS 和 DISP_STATUS 指针被声明为易失性的。这是必要的,因为程序仅读取相应位置的内容。没有数据写入这些位置。优化编译器可能会删除看似没有影响的程序语句,其中包括引用内存中已读取但从未写入的位置的语句。由于内存映射的 KBD_STATUS 和 DISP_STATUS 寄存器的内容会在程序外部的影响下发生变化,因此必须告知编译器这一事实。编译器不会删除涉及指针或其他声明为易失性变量的语句。
上面的重点是我的。
重新表述我上面的问题,我不明白优化编译器如何或为什么可以删除引用 KBD_STATUS 和 DISP_STATUS 的程序语句而不改变程序的语义。也就是说,为什么编译器会删除专门涉及这两个内存位置的语句?或者是 Hamacher 等人。而是说编译器可能会选择第一次从这些内存位置read(一种说法,我知道这意味着“翻译成这样的程序集......”),然后假设这个内存位置不会改变,因此不需要生成来自这些内存位置的后续汇编读取指令?
他们正在总结几个案例。
while ((*KBD_STATUS & 0x2) == 0);
可以像以前那样编译
if ((*KBD_STATUS & 0x2) == 0) {
while (1) ; // infinite loop
}
在
while ((*KBD_STATUS & 0x2) == 0);
中,程序使用*KBD_STATUS
。这段代码没有任何改变*KBD_STATUS
。默认情况下,C 程序中的对象不会更改,除非程序更改它。因此,如果没有 volatile
,编译器可以得出结论 *KBD_STATUS
永远不会改变,因此不需要多次从内存中读取它。因此编译器可能会优化这段代码:
if ((*KBD_STATUS & 0x2) == 0)
while (1) {} // Loop forever.
但是,
*KBD_STATUS
可以改变。当用户输入内容时,硬件会发生变化*KBD_STATUS
。这发生在程序代码之外。我们告诉编译器可能发生的情况的方法是将类型限定为 volatile
。
在
*DISP_DATA = ch;
中,程序中没有任何内容使用*DISP_DATA
。默认情况下,更改程序中的对象不会导致程序外发生任何事情。并且程序中没有其他内容使用 *DISP_DATA
,因此它不会产生任何效果。因此,编译器可能会得出结论,该赋值没有执行任何操作,并将其删除。然而,改变 *DISP_DATA
会产生一些效果;它改变了显示设备。这也会发生在程序之外,因此我们使用 volatile
告诉编译器更改 *DISP_DATA
会在程序之外产生影响。