什么是序列点,它们与未定义的行为有何关系?
什么是“序列点”?
未定义行为和序列点之间有什么关系?
我经常使用诸如a[++i] = i;
之类有趣且令人费解的表达方式,让自己感觉好一点。我为什么要停止使用它们?
如果您已阅读本文,请务必访问后续问题未定义的行为和重新加载的序列点。
(Note: This is meant to be an entry to Stack Overflow's C++ FAQ. If you want to critique the idea of providing an FAQ in this form, then the posting on meta that started all this would be the place to do that. Answers to that question are monitored in the C++ chatroom, where the FAQ idea started out in the first place, so your answer is very likely to get read by those who came up with the idea.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
C++98 和 C++03
此答案适用于旧版本的 C++ 标准。该标准的 C++11 和 C++14 版本并未正式包含“序列点”;操作被“先排序”或“未排序”或“不确定排序”代替。净效应本质上是相同的,但术语不同。
免责声明:TL'DR。
先决条件:C++ 标准的基本知识
什么是序列点?
标准说
副作用?什么是副作用?
表达式的求值会产生一些结果,如果此外执行环境的状态发生变化,则可以说该表达式(其求值)具有一些副作用。
例如:
除了初始化操作之外,由于
++
运算符的副作用,y
的值也会发生变化。到目前为止,一切都很好。继续讨论序列点。 comp.lang.c 作者 Steve Summit 给出的 seq-points 交替定义:
C++ 标准中列出的常见序列点有哪些?
它们是:
在完整表达式求值结束时 (
§1.9/16
)(完整表达式是不是另一个表达式的子表达式的表达式。)1示例:
在第一个表达式求值之后对以下每个表达式求值 (
§1.9/18
) 2a && b (§5.14)
a , b (§5.18)
(这里 a , b 是逗号运算符;在func(a,a++)
,
中是不是逗号运算符,它只是参数a
和a++
之间的分隔符,因此在这种情况下行为未定义(如果考虑a
)。成为原始类型))在函数调用时(无论函数是否内联),在评估所有函数参数(如果有)之后
在执行函数体中的任何表达式或语句之前发生 (
§1.9/17
)。1 :注意:完整表达式的计算可以包括非词法子表达式的计算
完整表达的一部分。例如,计算默认参数表达式(8.3.6)所涉及的子表达式被认为是在调用函数的表达式中创建的,而不是在定义默认参数的表达式中创建的
2:指示的运算符是内置运算符,如第 5 节中所述。当这些运算符之一在有效上下文中重载(第 13 节)时,从而指定用户定义的运算符函数,表达式指定函数调用,并且操作数形成参数列表,它们之间没有隐含的序列点。
什么是未定义行为?
该标准将
§1.3.12
部分中的未定义行为定义为3:允许的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式进行行为(带有或带有-
发出诊断消息),终止翻译或执行(发出诊断消息)。
未定义行为和序列点之间的关系是什么?
在我开始讨论之前,您必须了解 未定义行为、未指定行为和实现定义的行为。
您还必须知道
各个运算符的操作数和各个表达式的子表达式的求值顺序以及副作用发生的顺序是未指定的
。例如:
另一个例子 这里。
现在
§5/4
中的标准说这是什么意思?
非正式地,这意味着在两个序列点之间不得多次修改变量。
在表达式语句中,
下一个序列点
通常位于终止分号处,而前一个序列点
位于上一个语句的末尾。表达式还可以包含中间序列点
。从上面的句子中,以下表达式调用未定义的行为:
但以下表达式很好:
这是什么意思?这意味着如果在完整表达式中写入对象,则同一表达式中对其的任何和所有访问都必须直接参与要写入的值的计算。
例如,在
i = i + 1
中,i
的所有访问(左轴和右轴)都直接参与计算被写下来。所以没关系。该规则有效地将法律表达式限制为访问明显先于修改的表达式。
示例 1:
示例 2:
不允许,因为对
i
的访问之一(a[i]
中的访问)与最终存储的值无关在 i 中(这发生在i++
中),因此没有好的方法来定义(无论是我们的理解还是编译器的理解)访问应该在存储递增值之前还是之后进行。所以行为是未定义的。示例 3:
C++11 的后续答案此处< /a>.
C++98 and C++03
This answer is for the older versions of the C++ standard. The C++11 and C++14 versions of the standard do not formally contain 'sequence points'; operations are 'sequenced before' or 'unsequenced' or 'indeterminately sequenced' instead. The net effect is essentially the same, but the terminology is different.
Disclaimer : TL'DR.
Pre-requisites : An elementary knowledge of C++ Standard
What are Sequence Points?
The Standard says
Side effects? What are side effects?
Evaluation of an expression produces something and if in addition there is a change in the state of the execution environment it is said that the expression (its evaluation) has some side effect(s).
For example:
In addition to the initialization operation the value of
y
gets changed due to the side effect of++
operator.So far so good. Moving on to sequence points. An alternation definition of seq-points given by the comp.lang.c author
Steve Summit
:What are the common sequence points listed in the C++ Standard?
Those are:
at the end of the evaluation of full expression (
§1.9/16
) (A full-expression is an expression that is not a subexpression of another expression.)1Example :
in the evaluation of each of the following expressions after the evaluation of the first expression (
§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
anda++
. Thus the behaviour is undefined in that case (ifa
is considered to be a primitive type))at a function call (whether or not the function is inline), after the evaluation of all function arguments (if any) which
takes place before execution of any expressions or statements in the function body (
§1.9/17
).1 : Note : the evaluation of a full-expression can include the evaluation of subexpressions that are not lexically
part of the full-expression. For example, subexpressions involved in evaluating default argument expressions (8.3.6) are considered to be created in the expression that calls the function, not the expression that defines the default argument
2 : The operators indicated are the built-in operators, as described in clause 5. When one of these operators is overloaded (clause 13) in a valid context, thus designating a user-defined operator function, the expression designates a function invocation and the operands form an argument list, without an implied sequence point between them.
What is Undefined Behaviour?
The Standard defines Undefined Behaviour in Section
§1.3.12
as3 : permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or with-
out the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
What is the relation between Undefined Behaviour and Sequence Points?
Before I get into that you must know the difference(s) between Undefined Behaviour, Unspecified Behaviour and Implementation Defined Behaviour.
You must also know that
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
.For example:
Another example here.
Now the Standard in
§5/4
saysWhat does it mean?
Informally it means that between two sequence points a variable must not be modified more than once.
In an expression statement, the
next sequence point
is usually at the terminating semicolon, and theprevious sequence point
is at the end of the previous statement. An expression may also contain intermediatesequence points
.From the above sentence the following expressions invoke Undefined Behaviour:
But the following expressions are fine:
What does it mean? It means if an object is written to within a full expression, any and all accesses to it within the same expression must be directly involved in the computation of the value to be written.
For example in
i = i + 1
all the access ofi
(in L.H.S and in R.H.S) are directly involved in computation of the value to be written. So it is fine.This rule effectively constrains legal expressions to those in which the accesses demonstrably precede the modification.
Example 1:
Example 2:
is disallowed because one of the accesses of
i
(the one ina[i]
) has nothing to do with the value which ends up being stored in i (which happens over ini++
), and so there's no good way to define--either for our understanding or the compiler's--whether the access should take place before or after the incremented value is stored. So the behaviour is undefined.Example 3 :
Follow up answer for C++11 here.
这是我之前的回答的后续内容。并包含 C++11 相关材料。。
先决条件:关系(数学)的基本知识。
C++11 中真的没有序列点吗?
是的!这是真的。
序列点已替换为之前排序和之后排序(以及未排序和不确定排序) strong>) C++11 中的关系。
这个“之前排序”到底是什么?
顺序之前(§1.9/13)是一个关系,它是:
由单个线程执行的评估之间,并产生严格的偏序1
形式上,这意味着给定任意两个评估(见下文)
A
和B
,如果A
先于B
执行,则A
的执行应先于B
的执行。如果A
未在B
之前排序,且B
未在A
之前排序,则A 和
B
是无序 2。当
A
在B
之前排序时,评估A
和B
是不确定排序的B
排在A
之前,但未指定是哪个3。<子>[注释]
<子>
1 : 严格偏序是一个 二元关系
" <"
超过一个不对称<的集合
P
/code>和
transitive
,即P
中的所有a
、b
和c
,我们有:<子>........(i)。如果 a < b 然后 ← (b < a)(
不对称
);........(二)。如果 a < b 且 b < c 那么 a < c(
传递性
)。2:未排序的评估的执行可以重叠。
3:不确定顺序的评估不能重叠,但可以先执行其中一个。
C++11 上下文中“评估”一词的含义是什么?
在 C++11 中,表达式(或子表达式)的计算通常包括:
值计算(包括确定 glvalue 评估 并获取先前分配给对象的值 纯右值评估)和
引发副作用。
现在(§1.9/14)说:
简单的例子:
int x;
x = 10;
++x;
与
++x
相关的值计算和副作用在x = 10;
的值计算和副作用之后排序;因此Undefined Behaviour 和上述事情之间一定有某种关系吧?
是的!对。
在(§1.9/15)中提到
例如:
+
运算符的操作数的计算相对于彼此是无序的。<<
和>>
运算符的操作数的求值相对于彼此是无序的。4:在执行过程中多次求值的表达式中
对于程序来说,其子表达式的未排序和不确定排序计算不需要在不同的计算中一致地执行。
这意味着在
x + y
中,x
和y
的值计算在(x + y)< 的值计算之前排序。 /代码>。
更重要的是
示例:
i = i++ * ++i; // 未定义行为
i = ++i + i++; // 未定义行为
i = ++i + ++i; // 未定义行为
i = v[i++]; // 未定义的行为
i = v[++i]: // 明确定义的行为
i = i++ + 1; // 未定义行为
i = ++i + 1; // 明确定义的行为
++++i; // 明确定义的行为
f(i = -1, i = -1); // 未定义的行为(见下文)
表达式
(5)
、(7)
和(8)
不会调用未定义的行为。查看以下答案以获得更详细的解释。最终说明< /strong> :
如果您发现帖子中有任何缺陷,请发表评论。高级用户(代表数>20000)请随时编辑帖子以纠正拼写错误和其他错误。
This is a follow up to my previous answer and contains C++11 related material..
Pre-requisites : An elementary knowledge of Relations (Mathematics).
Is it true that there are no Sequence Points in C++11?
Yes! This is very true.
Sequence Points have been replaced by Sequenced Before and Sequenced After (and Unsequenced and Indeterminately Sequenced) relations in C++11.
What exactly is this 'Sequenced before' thing?
Sequenced Before(§1.9/13) is a relation which is:
between evaluations executed by a single thread and induces a strict partial order1
Formally it means given any two evaluations(See below)
A
andB
, ifA
is sequenced beforeB
, then the execution ofA
shall precede the execution ofB
. IfA
is not sequenced beforeB
andB
is not sequenced beforeA
, thenA
andB
are unsequenced 2.Evaluations
A
andB
are indeterminately sequenced when eitherA
is sequenced beforeB
orB
is sequenced beforeA
, but it is unspecified which3.[NOTES]
1 : A strict partial order is a binary relation
"<"
over a setP
which isasymmetric
, andtransitive
, i.e., for alla
,b
, andc
inP
, we have that:........(i). if a < b then ¬ (b < a) (
asymmetry
);........(ii). if a < b and b < c then a < c (
transitivity
).2 : The execution of unsequenced evaluations can overlap.
3 : Indeterminately sequenced evaluations cannot overlap, but either could be executed first.
What is the meaning of the word 'evaluation' in context of C++11?
In C++11, evaluation of an expression (or a sub-expression) in general includes:
value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and
initiation of side effects.
Now (§1.9/14) says:
Trivial example:
int x;
x = 10;
++x;
Value computation and side effect associated with
++x
is sequenced after the value computation and side effect ofx = 10;
So there must be some relation between Undefined Behaviour and the above-mentioned things, right?
Yes! Right.
In (§1.9/15) it has been mentioned that
For example :
+
operator are unsequenced relative to each other.<<
and>>
operators are unsequenced relative to each other.4: In an expression that is evaluated more than once during the execution
of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations.
That means in
x + y
the value computation ofx
andy
are sequenced before the value computation of(x + y)
.More importantly
Examples:
i = i++ * ++i; // Undefined Behaviour
i = ++i + i++; // Undefined Behaviour
i = ++i + ++i; // Undefined Behaviour
i = v[i++]; // Undefined Behaviour
i = v[++i]: // Well-defined Behavior
i = i++ + 1; // Undefined Behaviour
i = ++i + 1; // Well-defined Behaviour
++++i; // Well-defined Behaviour
f(i = -1, i = -1); // Undefined Behaviour (see below)
Expressions
(5)
,(7)
and(8)
do not invoke undefined behaviour. Check out the following answers for a more detailed explanation.Final Note :
If you find any flaw in the post please leave a comment. Power-users (With rep >20000) please do not hesitate to edit the post for correcting typos and other mistakes.
C++17 (
N4659
) 包含提案 细化惯用 C++ 的表达式求值顺序它定义了更严格的表达式求值顺序。
特别是下面的句子
以及以下说明
使先前未定义行为的几种情况有效,包括有问题的情况:
然而,其他几种类似的情况仍然会导致未定义的行为。
在
N4140
中:但在
N4659
中当然,使用符合 C++17 的编译器并不一定意味着应该开始编写这样的表达式。
C++17 (
N4659
) includes a proposal Refining Expression Evaluation Order for Idiomatic C++which defines a stricter order of expression evaluation.
In particular, the following sentence
together with the following clarification
make several cases of previously undefined behavior valid, including the one in question:
However several other similar cases still lead to undefined behavior.
In
N4140
:But in
N4659
Of course, using a C++17 compliant compiler does not necessarily mean that one should start writing such expressions.
我猜这种变化有一个根本原因,它不仅仅是为了使旧的解释更清晰而做的:这个原因是并发性。未指定的阐述顺序只是从几种可能的串行顺序中选择一种,这与之前和之后的排序有很大不同,因为如果没有指定的顺序,则可以并发评估:而旧规则则不然。例如:
先是 a 然后 b,或者 b 然后 a。现在,a 和 b 可以使用交错的指令甚至在不同的内核上进行评估。
I am guessing there is a fundamental reason for the change, it isn't merely cosmetic to make the old interpretation clearer: that reason is concurrency. Unspecified order of elaboration is merely selection of one of several possible serial orderings, this is quite different to before and after orderings, because if there is no specified ordering, concurrent evaluation is possible: not so with the old rules. For example in:
previously either a then b, or, b then a. Now, a and b can be evaluated with instructions interleaved or even on different cores.
在
C99(ISO/IEC 9899:TC3)
中,到目前为止,该讨论似乎没有出现,以下是关于评估顺序的 steteents。In
C99(ISO/IEC 9899:TC3)
which seems absent from this discussion thus far the following steteents are made regarding order of evaluaiton.该标准规定,只有当优化转换不会显着影响任何定义的程序的行为时,才可以执行优化转换。序列点规则被编写为允许以不交叉序列点的方式对动作进行重新排序,即使这种重新排序的效果可能是可观察到的,通过将任何可能导致某个动作的效果的动作分类为未定义行为。观察到允许的转变。
这种规则制定方法的一个不幸的后果是,它使得程序有必要明确强制执行操作的顺序,即使在不重要的情况下也是如此。例如,Java可以在不使用任何内存屏障的情况下缓存字符串的哈希码;缺乏内存屏障可能会导致线程感知到哈希码没有被缓存,即使在另一个线程实际缓存了它之后,从而执行冗余哈希值计算,但偶尔的额外计算的成本通常会很大低于每次访问时添加内存屏障的成本。然而,在 C 中,当另一个线程正在修改缓存的哈希码字段时尝试读取它会产生未定义行为,即使在读取尝试的唯一可能影响是生成旧值(表明哈希码不是“”的平台上)也是如此。 t 缓存)或最后写入的值(始终是正确的哈希码)。
The Standard specifies that optimizing transforms may be performed if any only if they do not observably affect the behavior of any defined program. The sequence-point rules are written to allow reordering of actions in ways that don't cross sequence points, even if the effects of such reordering might be observable, by classifying as Undefined Behavior any actions that would make it possible for the effects of an allowable transformation to be observed.
An unfortunate consequence of this approach to rule making is that it makes it necessary for programs to explicitly force the sequencing of actions even in cases where it wouldn't matter. For example, Java can cache strings' hash codes without using any memory barriers; the lack of memory barriers may cause a thread to perceive that the hash code isn't cached, even after another thread has actually cached it, and thus perform a redundant hash value computation, but the cost of the occasional extra calculations will generally be significantly below the cost of adding a memory barrier on every access. In C, however, attempting to read the cached hash code field while another thread is modifying it would yield Undefine Behavior, even on platforms where the only possible effects of the read attempt would be to yield the old value (indicating the hash code wasn't cached) or the last value written (which would always be the correct hash code).