欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > volatile再深入

volatile再深入

2025/4/19 16:53:50 来源:https://blog.csdn.net/Damon_X/article/details/147335818  浏览:    关键词:volatile再深入

volatile 是一个在 C/C++ 语言中非常特殊的关键字,用于告诉编译器:

这个变量的值可能在程序的控制范围之外被改变,请不要优化它的读取或写入


它的作用可以简单总结为一句话:

避免编译器对某个变量的访问进行“优化缓存”,强制每次都从内存读取。

volatile int flag = 0;void wait() {while (flag == 0) {// do something}
}

 上面这段代码中,如果没有 volatile,编译器可能会这样优化:

你一直没改变 flag,那我只读取一次好了,放在寄存器里一直用。

结果如果别的线程或硬件中断 在后台改变了 flag 的值,主线程永远也看不到,程序就卡死了。

加了 volatile,就告诉编译器:

别优化这个变量,每次循环都从内存重新读一次

常见使用场景

场景原因
多线程共享变量(不加锁)避免寄存器缓存导致的不可见
硬件寄存器(如 MCU、驱动)确保读/写的是真实寄存器值
中断服务程序中的变量ISR 修改变量,主线程要看到
信号量轮询类似上面的 flag 例子

注意:volatile ≠ 原子性

这是很多人误解的点:

volatile int count;

不代表它是“线程安全”的。volatile 只能防止优化,不能保证原子操作

如果你要实现多线程安全,还需要:

  • std::atomic(C++11 起)

  • 或者加锁(mutex)


和硬件相关

在驱动或者 SoC 寄存器层面:

#define DP_PHY_CTRL    (*((volatile uint32_t*)0xF9008000))

这个 volatile 是必须的!

否则编译器可能优化掉:

DP_PHY_CTRL |= 0x01;
DP_PHY_CTRL |= 0x02;

 而只执行一次写入,结果寄存器状态错乱。

#define DP_PHY_CTRL    (*((volatile uint32_t*)0xF9008000))

的深入解读:

这句话的意思是:

0xF9008000 地址 当成一个指向 uint32_t 类型的指针,然后告诉编译器这个地址是 volatile 的,也就是:每次读取或写入它,必须真的去访问这个地址,不允许优化。


重点来了:为什么地址要 volatile

你注意到,这里的 volatile 修饰的是:

(volatile uint32_t*)0xF9008000

也就是说,不是变量是 volatile,而是“内存地址指向的内容是 volatile”

换句话说:

告诉编译器:“这个地址对应的是硬件寄存器,它的值可能随时变化,所以每次访问它都不能用缓存。”

问题的核心在哪里,来看这个例子: 

// 寄存器地址
#define REG (*((volatile uint32_t*)0xF9008000))// 先设置 bit 0
REG |= 0x01;// 再设置 bit 1
REG |= 0x02;

 这看上去是两步,但如果没有 volatile,编译器可能优化成:

temp = REG;
temp |= 0x03;  // 一次性处理两个 bit
REG = temp;

为什么这不行?

  • 你第一次 |= 0x01 的时候,实际上可能触发某个控制动作(比如上升沿)

  • 第二次 |= 0x02 可能是设置另一个功能位

  • 如果你只写一次 0x03,硬件可能根本不触发前一个控制流程,甚至认为这是非法写入


加上 volatile 的作用就是:

强制编译器生成这样的机器码:

LDR R1, [0xF9008000]   // 每一步都重新读
ORR R1, R1, #0x01
STR R1, [0xF9008000]LDR R1, [0xF9008000]
ORR R1, R1, #0x02
STR R1, [0xF9008000]

 类似的例子还有:

比如设置 GPIO:

GPIO_OUT |= 0x01;  // 拉高 GPIO 0
GPIO_OUT &= ~0x01; // 拉低 GPIO 0

总结重点:

问题答案
为什么对地址加 volatile告诉编译器“这个地址值不能缓存,每次都得真的访问”
如果不加会怎样?编译器可能合并、删除、重排访问,导致硬件行为错误
`= 0x01
是变量 volatile 还是地址 volatile?是地址指向的数据是 volatile,不是变量本身

这个细节在驱动开发、寄存器控制里非常关键。

所以要注意区分:

volatile 的位置 确实不一样,含义也不同。这也是很多人初学的时候容易混淆的地方。 

场景 1:变量是 volatile

volatile int count;

解释:

  • 这是定义了一个变量 count,它本身是 volatile

  • 编译器会认为:这个变量可能会被别的线程、硬件、信号、ISR 修改,所以:

    • 每次访问 count 都必须真的访问内存

    • 不可以把它放到寄存器里缓存、优化

    • 不可以做 dead store elimination(不能丢掉看起来没用的写入)

使用场景:

  • 多线程共享的标志变量

  • 中断服务程序(ISR)修改的变量

  • 例如轮询退出条件、状态标志


场景 2:地址是 volatile(常见于寄存器)

#define REG (*(volatile uint32_t*)0xF9008000)

解释:

  • 这里 REG 本质是一个 指向硬件地址的指针,你在访问这个地址上的值

  • volatile 是修饰这个地址所“指向的内存区域”,告诉编译器:

    “0xF9008000 这个地址上的内容可能会随时变化,不要缓存读取或合并写入!”

使用场景:

  • 控制寄存器访问(GPIO、DP、PHY、I2C 等)

  • 显存、MMIO 区域、Memory-mapped hardware register

  • 所有的 裸寄存器访问 都必须加上这个 volatile


对比表格

形式含义用途
volatile int count;count 是 volatile 变量,存储在内存中,每次读取/写入都不能优化软件级别的可变状态,如线程间共享变量
*(volatile uint32_t*)0xF9008000内存地址 0xF9008000 上的内容是 volatile,每次都必须真的访问这块内存硬件寄存器、MMIO 控制

拓展:你还能写出这种组合

volatile uint32_t* reg = (volatile uint32_t*)0xF9008000;
  • 这里是:reg 是一个 指向 volatile 的指针

  • 等价于说:“通过 reg 访问到的内容,不能被优化”。


总结口诀

变量是 volatile → 表示值可能被外部修改(线程/中断)
地址指向的是 volatile → 表示这块内存是特殊区域(寄存器/硬件),不能优化访问

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词