C++11中sequenced before规则与副作用完成的技术疑问
C++11 求值顺序相关问题解答
1. 关于sequenced before的推论是否正确?
你的推论完全正确。
在C++11及以后版本中,单线程内的sequenced before关系仅保证两点:
- 前一表达式(A)的所有值计算完成后,后一表达式(B)才开始任何值计算。
- 前一表达式(A)的所有副作用启动完成后,后一表达式(B)才开始任何副作用启动。
它并不要求前一表达式的所有副作用执行完毕后,后一表达式才能开始。副作用(比如内存写入)可以异步完成——甚至相对于表达式顺序乱序执行——只要它们的启动顺序符合规则即可。
以你给出的原子操作为例:
线程2中,操作C被sequenced before操作D。这意味着读取x的操作(C)完成值计算(以及任何副作用启动,不过读操作没有写入副作用)后,D才会开始计算自身的值(常量42)或启动写入y的操作。但D的写入副作用(将42存入y)可能在C的读操作完成前就执行完毕,因为规则只要求启动顺序,不要求副作用的完成顺序。
2. 对依赖sequence point的C++11之前旧代码的影响
C++11之前使用**sequence point(序列点)**概念,它的规则更严格:序列点之前的所有副作用必须完全执行完毕,序列点之后的副作用才能开始。
C++11的sequenced before模型更宽松,允许编译器进行更激进的优化,这可能会破坏依赖序列点行为的旧代码:
- 旧代码假设某条语句对变量的写入会在下一条语句中完全可见,现在可能出现写入尚未完成(编译器重排序或CPU乱序执行导致)的情况,即使两条语句满足
sequenced before关系。对于非原子变量,这种风险尤其高,因为编译器只要保证启动顺序,就可以重排序副作用。 - 旧代码如果仅依赖序列点来避免数据竞争(未使用 proper 同步机制),现在可能会出现未定义行为。
sequenced before仅管线程内的顺序,不保证跨线程的可见性——仍然需要原子操作、互斥锁或其他同步原语来确保线程间的一致性。
3. 内置赋值运算符顺序与通用求值规则是否矛盾?
两者不存在矛盾,这只是通用规则的具体、细化应用。
通用规则指出:操作数的值计算必须在表达式的副作用启动前完成。对于内置赋值运算符,其步骤的顺序拆解如下:
- 左右操作数的值计算:计算左右两侧的值,但不执行它们子表达式的副作用。例如
a = b++中,这一步会计算b的原始值(右操作数)和a的地址(左操作数),但不会执行b的自增。 - 左操作数的副作用:执行左操作数求值过程中产生的副作用。如果左操作数是
*ptr++这类表达式,这一步会执行ptr的自增。 - 赋值的副作用与值计算:执行对左操作数的写入(赋值的核心副作用),然后计算赋值表达式的值(即被赋予的值)。
所有步骤都遵循通用规则:值计算先完成,再启动副作用。这个拆解只是明确了赋值操作的特定顺序,它的排序比其他一些表达式更严格。
内容的提问来源于stack exchange,提问作者baltazar
相关产品推荐
相关产品推荐

