编译器优化乌龙——记一次死循环不进入问题
记一次死循环不生效问题
看如下代码,本意是我们模拟一次死循环,然后会在中断处理函数中更改waiting的值,更改waiting的值后,跳出死循环。
int waiting = 0;
while(waiting==0)
{
}
运行起来发现,程序根本就没有进入这个死循环,究其原因。
因为编译器将其优化了,正确做法应该是使用 volatile 关键字:在 C/C++ 中,volatile 关键字可以告诉编译器变量可能会在程序的控制之外改变,因此编译器不会优化掉对它的读写操作。
所以我们更改后为
volatile int waiting = 0;
while(waiting==0)
{
}
程序正常执行死循环。
volatile关键字
在C和C++编程语言中,volatile 关键字是一种类型修饰符,用于告诉编译器该变量的值可能会在程序的控制之外被改变。这意味着每次使用这个变量时,编译器都必须从内存中重新读取它的值,而不是使用寄存器中的值或者缓存的值。编译器不会对 volatile 变量进行优化,以确保程序的行为与变量可能的外部变化保持同步。
用途:
硬件寄存器访问:在嵌入式编程中,volatile 通常用于访问硬件寄存器,因为硬件寄存器的值可能会被外部设备或中断服务程序改变。
多线程编程:在多线程环境中,volatile 可以用于声明共享变量,以确保每次访问都是最新的值,防止编译器对这些变量进行优化。
信号处理:在信号处理函数中,volatile 可以用于声明全局变量,以确保在主程序和信号处理程序之间正确同步变量的值。
编译器行为:
编译器不会对 volatile 变量进行寄存器优化,即不会将变量的值存储在寄存器中,而是每次访问时都从内存中读取。
编译器不会对 volatile 变量进行指令重排优化,即不会改变涉及 volatile 变量的指令的执行顺序。
内存模型:
在C++11及以后的版本中,volatile 被分为两种:C/C++风格的 volatile 和C++11中的 std::atomic。std::atomic 提供了更严格的内存模型保证,而 volatile 只保证变量的值可能在程序的控制之外改变。
使用示例:
volatile int flag = 0;
void interruptHandler() {
// 这个中断服务程序可能会改变flag的值
flag = 1;
}
void main() {
while (flag == 0) {
// 即使flag的值没有改变,编译器也会在每次循环时重新读取flag的值
// 因为flag被声明为volatile
}
// 处理flag为1的情况
}