全局变量加/不加 volatile 的影响
内容提要
全局变量问题由编译器优化导致。使用关键字“volatile”可以避免优化,确保每次直接读取其值。常见使用情况有访问外设和多线程共享变量。未声明为volatile的变量可能导致代码陷入循环或表现异常。
延伸解读
编译器优化如何引发全局变量问题
文章指出,全局变量问题常由编译器优化导致。编译器会假定未用volatile修饰的变量值不会在定义范围外被修改,从而可能删除或重排访问指令。这种优化在普通程序中通常无害,但在涉及硬件状态或并发访问时,会导致代码行为异常,例如轮询循环无法退出。
volatile的适用场景与必要性
文章列举了volatile的常见使用场景:访问内存映射外设、多线程共享全局变量、中断例程或信号处理程序中的全局变量。在STM32代码中,__IO即volatile的宏定义。对于跑系统的项目,线程间共享的全局变量建议加上volatile,但很多人并未注意这一点。
未使用volatile的潜在风险
未用volatile声明变量时,编译器可能执行不必要的优化,表现为:轮询硬件时陷入循环、多线程代码行为奇怪、故意时序延迟的代码被删除。文章举例说明,一个简单的延时函数在不同优化等级或不同编译器(如Keil和IAR)下,延时时间可能不同,因为循环可能被优化。
优化等级与编译环境的影响
文章强调,在不同优化等级和编译环境下,代码表现可能不同。同样的延时函数,在Keil和IAR下编译出的延时时间可能有差异。要深入理解这些差异,需要查看编译后的汇编代码,它能直观呈现优化带来的变化。
Q&A
全局变量不加 volatile 会导致什么问题?
编译器会假定该变量的值不会在其定义范围之外被修改,从而可能执行不必要的优化,导致代码陷入循环、多线程行为异常或删除故意设计的延时。
volatile 关键字的作用是什么?
volatile 是一种类型修饰符,作为指令关键字,确保该指令不会被编译器优化省略,并要求每次直接读取变量的值。
哪些情况下需要使用 volatile 声明变量?
常见情况包括:访问内存映射外设、在多个线程之间共享全局变量、在中断例程或信号处理程序中访问全局变量。
为什么不同优化等级或编译环境下延时函数的延时时间会不同?
因为编译器可能对未用 volatile 声明的变量进行优化,删除或改变实现延时的代码,导致在不同优化等级(如 Keil 和 IAR)下编译出的延时时间不一致。
在 STM32 代码中,__IO 和 volatile 有什么关系?
在 STM32 代码中,__IO 就是 volatile 的别名,用于修饰可能被意外修改的变量,常见于访问内存映射外设。
多线程共享全局变量时,为什么建议加 volatile?
因为编译器可能假定未用 volatile 声明的变量值不会在定义范围外被修改,从而进行优化,导致多线程代码表现出奇怪的行为。加上 volatile 可以确保每次直接读取其值,避免优化带来的问题。