什么是序列点,它们与未定义的行为有何关系?
什么是“序列点”?
未定义的行为和序列点之间的关系是什么?
我经常使用有趣且令人费解的表情,例如 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。
前提条件:
什么是序列点?
标准说
副作用?什么是副作用?
对表达式的评估会产生某些东西,如果另外,执行环境的状态发生了变化,则据说表达式(其评估)具有一定的副作用。
例如:
除了初始化操作外,由于
+> ++
运算符的副作用,y
的值也会更改。到目前为止,一切都很好。转到序列点。 Comp.lang.c作者给出的SEQ点的交替定义
Steve Summit
:C ++标准中列出的常见序列点是什么?
这些是:
在评估完整表达式的末尾(
§1.9/16
)(全表达是一种表达式,不是另一个表达式的子表达。) 1示例:
在评估第一个表达式之后的每个表达式(
§1.9/18
)之后的评估中a&& B(§5.14)
a || B(§5.15)
a? B:C(§5.16)
a,b(§5.18)
(这里a,b是逗号运算符; infunc(a,a ++)
a a ++ 之间的分离器。成为原始类型)),
is不是逗号运算符,它仅仅是参数发生在执行功能主体中的任何表达式或语句之前(
§1.9/17
)。全表达的一部分。例如,在评估默认参数表达式(8.3.6)中涉及的子表达在调用函数的表达式中创建,而不是定义默认参数的
表达如第5条所述,内置运算符。当这些运算符之一在有效上下文中超载(第13条)时,因此指定了用户定义的运算符函数时,该表达式指定了功能调用,并且操作数构成了参数列表,没有它们之间的隐含序列。
什么是未定义的行为?
该标准在
§1.3.12
中定义了未定义的行为为3:允许的不确定行为范围从完全忽略情况以不可预测的结果忽略情况,到在翻译或程序执行过程中以有记录的方式执行环境的特征(具有或使用 -
发出诊断消息的发放),终止翻译或执行(发出诊断消息)。
未定义的行为和序列点之间有什么关系?
在我了解之前,您必须知道未定义的行为,未指定的行为和未指定的行为和行为和行为,实施定义的行为。
您还必须知道,
评估单个操作员的操作数和单个表达式的子表达方式,以及发生副作用的顺序,未指定
。例如:
另一个示例在这里。
现在
§5/4
中的标准说这是什么意思?
非正式地,这意味着两个序列之间的变量不得多次修改。
在表达式语句中,
下一个序列点
通常位于终止的半隆上,上一个序列点
在上一个语句的结尾处。表达式还可以包含中间序列点
。从上面的句子中,以下表达式调用了不确定的行为:
但是以下表达式很好:
这是什么意思?这意味着,如果将对象写入完整表达式中,则必须在同一表达式内对其进行任何访问,必须直接参与要编写的值的计算。
例如,在
i = i + 1
中,i
的所有访问(在LHS和RHS中)直接参与了值的计算 写。所以很好。该规则有效地将法律表达限制在访问权限之前的修改之前。
示例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中没有序列点确实存在吗?
是!这是真的。
序列点已在和 后被测序取代()( notecorded 和不确定地测序)关系 C ++ 11中。
这是什么“序列”的东西是什么?
在 之前测序(§1.9/13)是一个关系,是:
在由单个 1
正式表示任何两个评估(请参阅下文)
a
a 和b
,如果a
是在b
之前测序,则执行a
应在之前执行b
。如果a
在b
和b
之前未进行测序,则在a
之前未对进行测序,则a
a 代码>和b
是未续订 2 。评估
a
和b
是不确定测序的当a
是在b
之前对进行测序时b
是在a
之前进行了测序,但未指定的是 3 。[NOTES]
1:严格的部分顺序是 二进制关系 &lt;“ 在集合上
p
,andtrastive
,即,即,即所有a
,b
和c
在p
中,我们有:........(i)。如果A&lt; b然后¬(b&lt; a)(
不对称
);........(ii)。如果A&lt; B和B&lt; C然后A&lt; C(
传递性
)。2:执行未序列评估可以重叠。
3:不确定测序的评估不能重叠,但可以首先执行。
在C ++ 11的上下文中,“评估”一词的含义是什么?
在C ++ 11中,一般对表达式(或子表达)的评估包括:
值计算(包括确定 glvalue评估并获取先前分配给对象的值/stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-lvalues-xvalues-glvalues-glvalues-and-prvalues“> prvalue评估)和
副作用的启动。
现在(§1.9/14)说:
琐碎的示例:
int x;
x = 10;
++ X;
值计算和与
++ x
关联的副作用在值计算和x = 10;
测序不确定的行为与上述事物之间必须有一定的关系,对吗?
是!对。
在(第1.9/15节)中,已经提到
例如:
+
运算符的操作数的评估相对于彼此而言是未序列的。&lt;&lt;
和&gt;&gt;
运营商相对于彼此而言是未序列的。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>:
如果您在帖子中发现任何缺陷,请发表评论。权力用户(使用REP&GT; 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然后B。现在,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)
中,该讨论中似乎没有关于evaluaiton的顺序进行以下结局。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).