一句话:"不是我这个函数亲自改的" = 加 volatile。
volatile 是给编译器的警告:这个变量可能在我看不见的地方被改,每次用到都必须去内存重新读,不许缓存优化。
k,中断在按键按下时 k++,定时器/另一线程可能 k-- → 多个程序外角色会改,必须加(个人实际场景)DMA_Flag,main 等它置位再读缓冲区 → ISR 是程序外角色(*(volatile uint32_t*)0x40021000) → 硬件随时变,防编译器把两次读合并成一次
0x40021000 是外设寄存器固定地址 → (uint32_t*) 强转成"指向 32 位无符号整数的指针" → volatile 标明指针指向的东西是 volatile(每次解引用必须真去内存读)→ * 解引用得到寄存器本体 → 外层 () 让它成为可读可写的左值。即 #define REG (*(volatile uint32_t*)0x40021000),把"裸地址"伪装成普通变量,但值由硬件控制uint32_t a = REG; uint32_t b = REG; return a==b;——编译器看遍代码发现没人改地址 0x40021000,判定内容没变,把 b=REG 优化成复用 a 的值 → a==b 恒真被优化掉。但实际两次读之间硬件可能已更新寄存器,检测逻辑被编译器干掉while (REG & FLAG_BUSY);——不加 volatile,编译器把读取提循环外,while(1) 死循环;加 volatile 每次循环强制读内存,硬件一清位就退出int count 只在 main() 里自己加加减减 → 程序内完全控制,不加| 易混点 A | 易混点 B | 关键区别 |
|---|---|---|
| volatile | const | volatile 防编译器缓存;const 防程序员误改;可共存于只读寄存器 |
| volatile | 原子操作 | volatile 管"每次读新值";原子管"读-改-写不被拆开";++ 分 LOAD/ADD/STORE 三步,可能被打断丢更新 |
| volatile | static | static 管存储期/作用域;static 变量被中断/线程共享时照样要加 volatile |
int count 只在 main() 里自己加加减减 → 不加(程序内控制,无外部改写)DMA_Flag,main 等它置位 → 加(ISR 是程序外角色;注意 flag 实际由 DMA 传输完成 ISR 置位,不是 DMA 硬件"顺便改",但"非 main 自己改"这条规则不变)while(!flag) 循环里没人改 flag → 编译器把 flag 缓存进寄存器,循环变 while(1)。volatile 强制每次去内存读。读内存比寄存器慢上百倍,所以缓存优化本身是合理的——错在编译器不知道"看不见的角色"在改它flag++ = LOAD(读内存)→ ADD(+1)→ STORE(写回)三步,中断可在 LOAD 后插入,导致丢更新(本应 2 实际 1)。解法:临界区(关中断)或硬件原子指令const volatile——const 防代码误写(防程序员),volatile 防缓存读到旧值(防编译器缓存)。两个防的敌人不同,所以只读寄存器两个都要2026-08-03
每主题一页,复习时只翻本目录。