40、const与volatile:C语言中的“冰与火”
说实话,const和volatile这两个关键字,单独拎出来都不算难。但把它们放在一起,很多同学就开始犯迷糊了。我当年刚学C语言时,也觉得这俩东西就像冰与火——一个说“你别动我”,一个说“我随时会变”。
嗯,今天咱们就把这对“冤家”彻底讲明白。
一、const修饰指针:到底谁不能变?
const修饰指针,说白了就三种情况。我习惯用一个口诀来记:“左定值,右定向,左右都定”。
咱们直接看代码:
int a = 10, b = 20;
// 情况1:const在*左边 —— 指针指向的值不能改
const int *p1 = &a;
*p1 = 30; // ❌ 错误!值不能改
p1 = &b; // ✅ 可以,指针本身可以改
// 情况2:const在*右边 —— 指针本身不能改
int * const p2 = &a;
*p2 = 30; // ✅ 可以,值能改
p2 = &b; // ❌ 错误!指针不能改
// 情况3:两边都有const —— 值和指针都不能改
const int * const p3 = &a;
*p3 = 30; // ❌ 错误!
p3 = &b; // ❌ 错误!
我在项目中遇到过最坑的一次:有个同事把const写在了*右边,结果想改指针指向时死活编译不过。他盯着代码看了半小时,最后发现是const位置写反了。你想想看,这种低级错误要是上了生产环境,排查起来得多痛苦?
const int *p 读作“p是一个指针,指向const int”。这样永远不会搞混。
二、volatile的作用:别让编译器“自作聪明”
volatile,说白了就是告诉编译器:“这个变量,你别给我优化,它随时可能变”。
编译器有时候挺“自作聪明”的。它觉得某个变量没被修改,就把值缓存到寄存器里。但有些场景下,变量确实会“莫名其妙”地变——比如硬件寄存器、多线程共享变量、信号处理函数中的变量。
看个典型例子:
// 假设这是一个硬件状态寄存器地址
#define STATUS_REG (*(volatile unsigned int *)0x40001000)
void wait_for_flag() {
while ((STATUS_REG & 0x01) == 0) {
// 等待硬件置位
}
}
如果不加volatile,编译器可能会把STATUS_REG的值读到寄存器里,然后一直判断那个寄存器值——硬件改了内存,但编译器不知道,程序就死循环了。我曾经在调试一个嵌入式设备时遇到过这问题,折腾了两天才发现是漏了volatile。
三、volatile的典型使用场景
我总结了一下,volatile主要用在三个地方:
- 硬件寄存器映射:比如上面那个状态寄存器
- 中断服务程序中修改的变量:主程序和中断都可能改它
- 多线程共享的全局标志:一个线程写,另一个线程读
举个例子:
volatile int g_flag = 0;
// 中断服务程序
void ISR_Handler() {
g_flag = 1; // 中断里修改
}
// 主循环
void main_loop() {
while (g_flag == 0) {
// 等待中断
}
// 处理事件
}
这里如果不加volatile,编译器可能把g_flag优化成永远为0,主循环就永远跳不出来了。
四、const与volatile的组合:看似矛盾,实则互补
const和volatile能同时修饰一个变量吗?能!而且很常见。
你想想看:一个硬件状态寄存器,它的值由硬件决定,程序不能改——这是const。但它的值随时可能变化——这是volatile。所以:
// 只读硬件寄存器
const volatile uint32_t *p_hw_reg = (const volatile uint32_t *)0x40001000;
uint32_t val = *p_hw_reg; // ✅ 可以读
*p_hw_reg = 0x01; // ❌ 错误!const禁止写
再比如,一个只读的全局时间戳,由RTC硬件更新:
const volatile time_t g_rtc_time; // 程序不能改,但硬件会改
我在做物联网设备时,就经常用这种组合来定义硬件寄存器映射。既保证了程序不会误写,又防止了编译器优化导致读不到最新值。
五、知识体系总览
下面这张图,把const和volatile的核心知识点串起来了:
六、避坑指南
最后,分享几个我踩过的坑:
- const变量被意外修改:通过指针强制类型转换可以绕过const检查,但后果自负。我曾经见过有人这么干,结果程序跑飞了。
- volatile变量被过度使用:不是所有变量都需要volatile。滥用会导致代码膨胀、性能下降。只在确实需要的地方加。
- const和volatile的顺序:
const volatile int和volatile const int是一样的,但建议统一写法,保持代码风格一致。
公众号:蓝海资料掘金营,微信deep3321