什么是“序列点”?
未定义的行为和序列点之间的关系是什么?
我经常使用 a[++i] = i;
类的有趣而复杂的表达,让自己感觉更好。为什么我要停止使用它们?
如果您已阅读本文,请务必访问后续问题 Undefined behavior and sequence points reloaded 。
(注意:这是对 Stack Overflow 的 C++ FAQ 的 一个条目。如果您想批评以这种形式提供常见问题解答的想法,那么 开始这一切的 meta 上的帖子就是这样 做的地方。该问题在 C++ 聊天室 中进行监控,FAQ 想法最初是从那里开始的,因此您的答案很可能会被提出该想法的人阅读。)
原文由 Prasoon Saurav 发布,翻译遵循 CC BY-SA 4.0 许可协议
C++98 和 C++03
此答案适用于旧版本的 C++ 标准。标准的 C++11 和 C++14 版本不正式包含“序列点”;操作是“先排序”或“未排序”或“不确定排序”。净效果基本相同,但术语不同。
免责声明:好的。这个答案有点长。所以阅读时要有耐心。如果你已经知道这些东西,再读一遍也不会让你发疯。
先决条件: C++标准 的基本知识
什么是序列点?
标准说
副作用?什么是副作用?
表达式的求值会产生一些东西,如果此外执行环境的状态发生变化,则表示该表达式(其求值)具有一些副作用。
例如:
除了初始化操作之外,由于
++
运算符的副作用,y
的值会发生变化。到目前为止,一切都很好。继续到序列点。 comp.lang.c 作者
Steve Summit
给出的 seq-points 的替代定义:C++ 标准中列出的常见序列点有哪些?
那些是:
§1.9/16
) (完整表达式是不是另一个表达式的子表达式的表达式。) 1例子 :
在对第一个表达式求值后对以下每个表达式求值 (
§1.9/18
) 2a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)
(here a , b is a comma operator; infunc(a,a++)
,
is not a comma operator, it’s merely a separator between the argumentsa
和a++
。因此在这种情况下行为是未定义的(如果a
被认为是原始类型)在函数调用(无论函数是否是内联函数)中,在对函数体中的任何表达式或语句(
§1.9/17
)执行之前发生的所有函数参数(如果有)的求值之后。1:注意:完整表达式的评估可以包括对在词法上不属于完整表达式的子表达式的评估。例如,评估默认参数表达式(8.3.6)所涉及的子表达式被认为是在调用函数的表达式中创建的,而不是在定义默认参数的表达式中创建的
2 : 指示的运算符是内置运算符,如第 5 节所述。当这些运算符之一在有效上下文中被重载(第 13 条),从而指定用户定义的运算符函数时,表达式指定函数调用和操作数形成一个参数列表,它们之间没有隐含的序列点。
什么是未定义行为?
该标准将
§1.3.12
部分中的未定义行为定义为3:允许的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止翻译或执行(伴随诊断信息的发布)。
简而言之,未定义的行为意味着从你的鼻子飞出的守护进程到你的女朋友怀孕, 任何事情都 可能发生。
未定义行为和序列点之间有什么关系?
在我开始之前,您必须知道 Undefined Behaviour、Unspecified Behavior 和 Implementation Defined Behavior 之间的区别。
您还必须知道
the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified
。例如:
另一个例子 在这里。
现在
§5/4
中的标准说这是什么意思?
非正式地,这意味着在两个序列点之间,一个变量不能被多次修改。在表达式语句中,
next sequence point
通常在终止分号处,而previous sequence point
在前一个语句的末尾。表达式还可以包含中间sequence points
。从上面的句子中,以下表达式调用未定义的行为:
但是下面的表达式很好:
这是什么意思?这意味着如果在完整表达式中写入对象,则在同一表达式中对其的任何和所有访问都 必须直接参与要写入的值的计算。
例如,在
i = i + 1
所有对i
的访问(在 LHS 和 RHS 中)都 直接参与了要写入的值的计算。所以没关系。该规则有效地将合法表达限制为那些访问明显先于修改的表达。
示例 1:
示例 2:
被禁止,因为
i
中的a[i]
)与最终存储在 i 中的值无关(这发生在i++
),因此没有好的方法来定义——无论是为了我们的理解还是编译器的——访问应该发生在存储增量值之前还是之后。所以行为是不确定的。示例 3:
在此处 跟进 C++11 的答案。