You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何volatile访问可与非volatile访问发生重排序?

这个问题问得特别准,刚好戳中了很多开发者对volatile的常见误解——不少人觉得它能搞定所有内存顺序的问题,但其实它的能力边界很明确。咱们一步步拆解来看:

先还原代码与汇编

首先把你的C代码和生成的汇编清晰列出来,方便对照:

// cordering.c
// 编译命令: gcc -O2 -S -masm=intel cordering.c
int A;
volatile int B;

void foo() {
    A = B + 1;
    B = 0;
}

对应的汇编代码:

mov eax, DWORD PTR B[rip]  ; 读取volatile变量B的值到寄存器eax
mov DWORD PTR B[rip], 0    ; 将volatile变量B设为0
add eax, 1                 ; 计算原来的B值+1
mov DWORD PTR A[rip], eax  ; 把结果赋值给非volatile变量A
ret

为什么会出现这种重排序?

核心原因是**volatile的语义只约束它自己,不管非volatile变量的访问顺序**:

  • volatile到底管什么? 它的核心作用是告诉编译器:「这个变量可能被当前线程之外的东西修改(比如硬件中断、其他线程),你不能对它的读写操作瞎优化」。具体来说:编译器不能省略对volatile变量的读写,不能把它的值缓存到寄存器后重复用(除非是同一个序列点内的连续访问),必须严格按照代码里的顺序执行对volatile变量的所有读写操作。
  • 非volatile变量的“自由” 对于像A这样的非volatile变量,编译器默认假设只有当前线程会碰它。只要最终程序的「可观测行为」(仅从volatile变量和I/O操作的角度看)不变,编译器可以随便调整非volatile变量访问的顺序,甚至做其他优化——毕竟这不会影响程序的正确输出。

回到你的例子:
原C代码里,对B的操作顺序是先读B(用来算A),再写B=0——汇编里对B的操作完全遵循这个顺序(先读再写),完全没违反volatile的规则。而对A的赋值,因为A是非volatile的,编译器知道把它放在B=0之后,不会改变程序的可观测结果(毕竟没人会盯着A的中间状态),所以就做了这个重排——本质上是编译器为了效率(比如利用寄存器减少内存访问)做的合理优化。

关键结论

volatile不是内存屏障,它不保证volatile访问和非volatile访问之间的顺序,只保证volatile变量自身的读写顺序不被编译器打乱。如果是多线程场景需要严格的内存顺序控制,别用volatile,应该用C标准<stdatomic.h>里的原子操作和内存序约束。

内容的提问来源于stack exchange,提问作者choxsword

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:45:21