被if(false)约束的非原子变量写操作是否构成内存数据竞争?C++标准界定与编译器行为分析
先看你给出的场景代码:
// 全局变量 int x = 0; // 非原子类型 // 线程1 x = 1; // 线程2 if (false) x = 2;
首先回到核心问题:根据C++标准,这段代码是否存在内存数据竞争?我们先明确标准里的判定规则:
C++标准[intro.races]条款规定:两个表达式求值存在冲突的条件是,其中一个修改某内存位置(4.4),另一个读取或修改同一内存位置。程序执行若包含两个潜在并发的冲突操作,且至少其中一个操作不是原子操作,同时二者不存在“先于发生”关系(信号处理程序的特殊情况除外),则该程序存在内存数据竞争,此类数据竞争会导致未定义行为。
接下来从两个角度分析:
一、语言合规(language-lawyer)视角:代码是安全的
这里的关键在于**“程序执行是否包含潜在并发的冲突操作”**。线程2里的x = 2分支条件是编译期就能确定的false,意味着这段赋值操作的求值永远不会成为程序实际执行的一部分。既然这个写操作根本不会被执行,那自然不存在“两个潜在并发的冲突操作”——线程1的写操作没有对应的并发冲突操作,所以完全符合标准,不存在内存数据竞争,代码是安全的。
二、技术实现视角:推测执行不会带来问题
那如果某个“特立独行”的编译器决定对这个永远不执行的写操作做推测执行呢?比如先临时把x写成2,等检查完false条件再回滚?
其实不用太担心:即使编译器真的这么做了,它也必须保证这个临时的写操作对其他线程是完全不可观察的。也就是说,线程1绝对看不到这个推测出来的x=2的值,否则就违反了C++内存模型的语义。而现实里,主流编译器对于编译期就能确定为false的分支,会直接把整个分支的代码彻底优化掉,根本不会生成相关的机器指令,更不会做什么推测执行。
顺便提一下你提到的C++11那个特殊例子:
// 线程1: r1 = y.load(std::memory_order_relaxed); if (r1 == 42) x.store(r1, std::memory_order_relaxed); // 线程2: r2 = x.load(std::memory_order_relaxed); if (r2 == 42) y.store(42, std::memory_order_relaxed);
标准允许这段代码出现r1==r2==42的结果,这确实是推测执行导致的——但和咱们的场景本质不同:这里的分支条件是运行时才能确定的,编译器/CPU可以合理推测分支成立并提前执行写操作,最后如果推测正确就保留结果,错误就回滚。而咱们的场景里分支是编译期死的false,不存在这种合理推测的基础。
内容的提问来源于stack exchange,提问作者JMC

