volatile 关键字

← C语言知识地图


作用

告诉编译器:该变量可能在程序控制之外被修改,每次访问都必须从内存读取,禁止编译器对其进行缓存优化(寄存器缓存、指令重排)。

RTOS中典型应用:ISR和任务共享变量(中断中修改flag,任务中轮询读取)、多任务共享全局变量、外设寄存器访问。


典型使用场景

场景说明
硬件寄存器外设寄存器的值随时可能被硬件改变
中断服务程序(ISR)共享变量主循环读取、ISR 写入的标志位
多线程共享变量(有限)防止编译器优化,但不保证原子性,不能替代锁/atomic
DMA缓冲区DMA控制器直接修改内存,需要将指针本身声明为 volatile
MMIO 操作Memory-mapped I/O 必须加 volatile

对于指针:

volatile int *p:p 指向的内容是 volatile 的,每次通过 p 读取值都从内存重读。p 本身不是 volatile。

int * volatile pp 本身是 volatile 的,意味着指针的值(即地址)可能被外部改变,每次使用 p 都重新读取指针值。

volatile int * volatile p:指针和指向的内容都是 volatile 的。


寄存器映射(外设寄存器)

外设寄存器挂在总线地址上,值会被硬件随时改变(如状态寄存器的「发送完成」位由外设硬件置位),所以用指针访问必须加 volatile

// 等发送完成:不加 volatile 会死循环
volatile uint32_t *status_reg = (volatile uint32_t *)0x40013800;
 
while ((*status_reg & 0x01) == 0);   // 等待就绪位

不加 volatile 为什么翻车:编译器发现循环体里没改 *status_reg,就认为它每次读都一样,把读取提到循环外只读一次,于是 while 永远卡在第一次读到的值(死循环 / 永不等待)。

加了 volatile,编译器强制每次循环真正去读硬件寄存器,才能看到硬件改的新值。

铁律:硬件寄存器地址必须用 volatile 修饰。 内核侧的特殊寄存器(MRS/MSR 访问、无地址)不需要 volatile,见 特殊寄存器


函数int square(volatile int *ptr)能实现预期目标吗?为什么?

函数 int square(volatile int *ptr) { return *ptr * *ptr; } 不能实现预期目标。

原因:因为 *ptr 是 volatile 的,编译器会两次从内存读取 *ptr 的值。如果在两次读取之间该值被中断或其他线程修改了,两次读到的值可能不同,结果就不是一个数的平方。

正确写法:

int square(volatile int *ptr) 
{    	int val = *ptr;  // 只读一次,存到局部变量  
	return val * val;
}