C++ 未定义的行为和序列点
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4176328/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Undefined behavior and sequence points
提问by Prasoon Saurav
What are "sequence points"?
什么是“序列点”?
What is the relation between undefined behaviour and sequence points?
未定义行为和序列点之间有什么关系?
I often use funny and convoluted expressions like a[++i] = i;
, to make myself feel better. Why should I stop using them?
我经常使用有趣和复杂的表达方式,例如a[++i] = i;
,让自己感觉更好。为什么我应该停止使用它们?
If you've read this, be sure to visit the follow-up question Undefined behavior and sequence points reloaded.
如果您已阅读本文,请务必访问后续问题Undefined behavior and sequence points reloaded。
(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 thiswould 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.)
(注意:这是Stack Overflow 的 C++ FAQ 的一个条目。如果你想批评以这种形式提供 FAQ 的想法,那么开始所有这一切的 meta 上的帖子将是这样做的地方。答案该问题在C++ 聊天室中受到监控,FAQ 的想法首先在这里开始,因此您的答案很可能会被提出该想法的人阅读。)
采纳答案by Prasoon Saurav
C++98 and C++03
C++98 和 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.
此答案适用于旧版本的 C++ 标准。该标准的 C++11 和 C++14 版本并未正式包含“序列点”;操作是“先排序”或“未排序”或“不确定排序”。净效应基本相同,但术语不同。
Disclaimer: Okay. This answer is a bit long. So have patience while reading it. If you already know these things, reading them again won't make you crazy.
免责声明:好的。这个答案有点长。所以阅读时要有耐心。如果你已经知道这些东西,再读一遍也不会让你发疯。
Pre-requisites: An elementary knowledge of C++ Standard
先决条件:C++标准的基本知识
What are Sequence Points?
什么是序列点?
The Standard says
标准说
At certain specified points in the execution sequence called sequence points, all side effectsof previous evaluations shall be complete and no side effectsof subsequent evaluations shall have taken place. (§1.9/7)
在称为序列点的执行序列中的某些指定点处,先前评估的所有副作用都应完成,并且不应发生后续评估的副作用。(第 1.9/7 节)
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:
例如:
int x = y++; //where y is also an int
In addition to the initialization operation the value of y
gets changed due to the side effect of ++
operator.
除了初始化操作之外,y
由于++
运算符的副作用,的值也会发生变化。
So far so good. Moving on to sequence points. An alternation definition of seq-points given by the comp.lang.c author Steve Summit
:
到现在为止还挺好。继续到序列点。comp.lang.c 作者给出的 seq-points 的替代定义Steve Summit
:
Sequence point is a point in time at which the dust has settled and all side effects which have been seen so far are guaranteed to be complete.
序列点是尘埃落定并且迄今为止看到的所有副作用都可以保证完成的时间点。
What are the common sequence points listed in the C++ Standard ?
C++ 标准中列出的常见序列点是什么?
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 :
int a = 5; // ; is a sequence point here
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.9/16
)的计算结束时(完整表达式是不是另一个表达式的子表达式的表达式。)1例子 :
int a = 5; // ; is a sequence point here
在计算第一个表达式 (
§1.9/18
) 2之后对以下每个表达式的计算a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)
(这里 a , b 是逗号运算符; infunc(a,a++)
,
不是逗号运算符,它只是参数a
和之间的分隔符a++
。因此在这种情况下行为是未定义的(如果a
被认为是原始类型))
在函数调用中(无论函数是否内联),在执行函数体中的任何表达式或语句之前发生的所有函数参数(如果有)的评估之后 (
§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
1 : 注意:完整表达式的求值可以包括不属于完整表达式的词法部分的子表达式的求值。例如,评估默认参数表达式(8.3.6)中涉及的子表达式被认为是在调用函数的表达式中创建的,而不是在定义默认参数的表达式中创建的
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.
2 :指示的运算符是内置运算符,如第 5 条所述。当这些运算符之一在有效上下文中重载(第 13 条),从而指定用户定义的运算符函数时,表达式指定函数调用和操作数形成一个参数列表,它们之间没有隐含的序列点。
What is Undefined Behaviour?
什么是未定义行为?
The Standard defines Undefined Behaviour in Section §1.3.12
as
该标准将部分中的未定义行为定义§1.3.12
为
behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements 3.
Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior.
行为,例如在使用错误的程序结构或错误数据时可能出现的行为,本国际标准对此没有强加要求3。
当本国际标准省略任何明确的行为定义的描述时,也可能会出现未定义的行为。
3 : 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).
3 : 允许的未定义行为范围从完全忽略具有不可预测结果的情况,在翻译或程序执行期间以环境特征的文件化方式(有或没有发出诊断消息),到终止翻译或执行(随着诊断消息的发布)。
In short, undefined behaviour means anythingcan happen from daemons flying out of your nose to your girlfriend getting pregnant.
简而言之,未定义的行为意味着任何事情都可能发生,从恶魔飞出你的鼻子到你女朋友怀孕。
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.
在我开始之前,您必须了解Undefined Behaviour, Unspecified Behavior 和 Implementation Defined Behavior之间的区别。
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
.
你也必须知道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:
例如:
int x = 5, y = 6;
int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.
Another example here.
另一个例子在这里。
Now the Standard in §5/4
says
现在标准§5/4
说
- 1) Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression.
- 1)在前一个和下一个序列点之间,标量对象的存储值最多应通过表达式的评估修改一次。
What 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 the previous sequence point
is at the end of the previous statement. An expression may also contain intermediate sequence points
.
非正式地,这意味着在两个序列点之间,一个变量不能被修改多次。在表达式语句中,thenext sequence point
通常位于终止分号处,而previous sequence point
is 位于前一条语句的末尾。表达式还可以包含中间sequence points
.
From the above sentence the following expressions invoke Undefined Behaviour:
从上面的句子中,以下表达式调用了未定义的行为:
i++ * ++i; // UB, i is modified more than once btw two SPs
i = ++i; // UB, same as above
++i = 2; // UB, same as above
i = ++i + 1; // UB, same as above
++++++i; // UB, parsed as (++(++(++i)))
i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)
But the following expressions are fine:
但是下面的表达没问题:
i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i); // well defined
int j = i;
j = (++i, i++, j*i); // well defined
- 2) Furthermore, the prior value shall be accessed only to determine the value to be stored.
- 2)此外,只能访问先前值以确定要存储的值。
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 of i
(in L.H.S and in R.H.S) are directly involved in computationof the value to be written. So it is fine.
例如,在i = i + 1
所有i
(在 LHS 和 RHS 中)的访问都直接参与要写入的值的计算。所以没关系。
This rule effectively constrains legal expressions to those in which the accesses demonstrably precede the modification.
该规则有效地将合法表达限制为访问明显先于修改的表达。
Example 1:
示例 1:
std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2
Example 2:
示例 2:
a[i] = i++ // or a[++i] = i or a[i++] = ++i etc
is disallowed because one of the accesses of i
(the one in a[i]
) has nothing to do with the value which ends up being stored in i (which happens over in i++
), 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.
是不允许的,因为i
(in 中的那个)的访问之一a[i]
与最终存储在 i 中的值(发生在 in 中i++
)无关,因此没有很好的方法来定义 - 无论是为了我们的理解还是编译器 - 访问应该在存储递增值之前还是之后进行。所以行为是未定义的。
Example 3 :
示例 3:
int x = i + i++ ;// Similar to above
Follow up answer for C++11 here.
在此处跟进 C++11 的答案。
回答by Prasoon Saurav
This is a follow up to my previous answerand contains C++11 related material..
这是我之前回答的后续内容,包含 C++11 相关材料。.
Pre-requisites: An elementary knowledge of Relations (Mathematics).
先决条件:基本的关系知识(数学)。
Is it true that there are no Sequence Points in C++11?
C++11中真的没有序列点吗?
Yes!This is very true.
是的!这是非常真实的。
Sequence Pointshave been replaced by Sequenced Beforeand Sequenced After(and Unsequencedand Indeterminately Sequenced) relationsin C++11.
序列点已被C++11 中的Sequenced Before和Sequenced After(以及Unsequenced和Indeterminately Sequenced)关系取代。
What exactly is this 'Sequenced before' thing?
这个“先排序”的东西到底是什么?
Sequenced Before(§1.9/13)is a relation which is:
Sequenced Before (§1.9/13)是一个关系,它是:
between evaluations executed by a single threadand induces a strict partial order1
在由单个线程执行的评估之间并引入严格的偏序1
Formally it means given any two evaluations(See below)A
and B
, if A
is sequenced beforeB
, then the execution of A
shall precedethe execution of B
. If A
is not sequenced before B
and B
is not sequenced before A
, then A
and B
are unsequenced2.
正式它意味着给定任意两个评价(见下文)A
和B
,如果A
是之前测序B
,则执行A
应当先执行B
。如果A
之前没有进行测序B
和B
之前没有进行测序A
,然后A
和B
在未测序2。
Evaluations A
and B
are indeterminately sequencedwhen either A
is sequenced before B
or B
is sequenced before A
, but it is unspecified which3.
评价A
和B
被不定测序当任一A
之前测序B
或B
之前测序A
,但它是未指定的,其3。
[NOTES]
1 : A strict partial order is a binary relation"<"
over a set P
which is asymmetric
, and transitive
, i.e., for all a
, b
, and c
in P
, 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 evaluationscan overlap.
3 : Indeterminately sequenced evaluationscannot overlap, but either could be executed first.
[注释]
1:严格偏序是一个二元关系"<"
在一组P
其是asymmetric
,和transitive
的,即,对于所有的a
,b
以及c
在P
中,我们有:
........(I)。如果 a < b 那么 ? (b < a) ( asymmetry
);
......(二)。如果 a < b 且 b < c 则 a < c ( transitivity
)。
2:无序评估的执行可以重叠。
3:不确定排序的评估不能重叠,但可以先执行。
What is the meaning of the word 'evaluation' in context of C++11?
在 C++11 的上下文中,“评估”这个词是什么意思?
In C++11, evaluation of an expression (or a sub-expression) in general includes:
在 C++11 中,表达式(或子表达式)的计算通常包括:
value computations(including determining the identity of an object for glvalue evaluationand fetching a value previously assigned to an object for prvalue evaluation) and
initiation of side effects.
Now (§1.9/14) says:
现在(第 1.9/14 节)说:
Every value computation and side effect associated with a full-expression is sequenced beforeevery value computation and side effect associated with the next full-expression to be evaluated.
与完整表达式关联的每个值计算和副作用在与要评估的下一个完整表达式关联的每个值计算和副作用之前排序。
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;
简单的例子:
int x;
x = 10;
++x;
相关
++x
的值计算和副作用在 的值计算和副作用之后排序x = 10;
So there must be some relation between Undefined Behaviour and the above-mentioned things, right?
那么Undefined Behavior和上面提到的东西之间一定有某种关系吧?
Yes!Right.
是的!对。
In (§1.9/15) it has been mentioned that
在 (§1.9/15) 中已经提到
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced4.
除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的评估是无序的4。
For example :
例如 :
int main()
{
int num = 19 ;
num = (num << 3) + (num >> 3);
}
- Evaluation of operands of
+
operator are unsequenced relative to each other. - Evaluation of operands of
<<
and>>
operators are unsequenced relative to each other.
- 运算
+
符的操作数的计算相对于彼此是无序的。 <<
和>>
运算符的操作数的计算相对于彼此是无序的。
4: In an expression that is evaluated more than once during the execution of a program, unsequencedand indeterminately sequencedevaluations of its subexpressions need not be performed consistently in different evaluations.
4:在程序执行期间被评估多次的表达式中,其子表达式的无序和不确定序列的评估不需要在不同的评估中一致执行。
(§1.9/15) The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
(§1.9/15) 运算符的操作数的值计算在运算符的结果的值计算之前被排序。
That means in x + y
the value computation of x
and y
are sequenced before the value computation of (x + y)
.
这意味着在x + y
的值计算中x
和y
在 的值计算之前被排序(x + y)
。
More importantly
更重要的是
(§1.9/15) If a side effect on a scalar object is unsequenced relative to either
(a) another side effect on the same scalar object
or
(b) a value computation using the value of the same scalar object.
the behaviour is undefined.
(§1.9/15) 如果对标量对象的副作用相对于任一
(a)对同一个标量对象的另一个副作用
或者
(b)使用相同标量对象的值进行值计算。
行为未定义。
Examples:
例子:
int i = 5, v[10] = { };
void f(int, int);
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)
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)
When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [Note:Value computations and side effects associated with different argument expressions are unsequenced. — end note]
调用函数时(无论函数是否内联),与任何参数表达式或指定被调用函数的后缀表达式相关的每个值计算和副作用,在执行函数体中的每个表达式或语句之前进行排序称为函数。[注意:与不同参数表达式相关的值计算和副作用是无序的。—尾注]
Expressions (5)
, (7)
and (8)
do not invoke undefined behaviour. Check out the following answers for a more detailed explanation.
Expressions (5)
,(7)
并且(8)
不会调用未定义的行为。查看以下答案以获得更详细的解释。
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.
如果您发现帖子中有任何缺陷,请发表评论。高级用户(代表 >20000)请不要犹豫编辑帖子以更正错别字和其他错误。
回答by AlexD
C++17(N4659
) includes a proposal Refining Expression Evaluation Order for Idiomatic C++which defines a stricter order of expression evaluation.
C++17( N4659
) 包括一个提案Refining Expression Evaluation Order for Idiomatic C++,它定义了更严格的表达式求值顺序。
In particular, the following sentence
特别是下面这句话
8.18 Assignment and compound assignment operators:
....In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. The right operand is sequenced before the left operand.
8.18 赋值和复合赋值运算符:
......在所有情况下,赋值顺序都在左右操作数的值计算之后,赋值表达式的值计算之前。 右操作数排在左操作数之前。
together with the following clarification
连同以下说明
An expression Xis said to be sequenced before an expression Yif every value computation and every side effect associated with the expression Xis sequenced before every value computation and every side effect associated with the expression Y.
如果每个值计算和与表达式X关联的每个副作用都在每个值计算和与表达式Y关联的每个副作用之前排序,则称表达式X在表达式Y之前排序。
make several cases of previously undefined behavior valid, including the one in question:
使以前未定义行为的几种情况有效,包括有问题的一种:
a[++i] = i;
However several other similar cases still lead to undefined behavior.
然而,其他几个类似的情况仍然会导致未定义的行为。
In N4140
:
在N4140
:
i = i++ + 1; // the behavior is undefined
But in N4659
但在 N4659
i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined
Of course, using a C++17 compliant compiler does not necessarily mean that one should start writing such expressions.
当然,使用符合 C++17 的编译器并不一定意味着应该开始编写此类表达式。
回答by Yttrill
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:
我猜想改变有一个根本的原因,使旧的解释更清晰不仅仅是表面上的:这个原因是并发性。未指定的细化顺序只是选择几种可能的串行排序之一,这与前后排序完全不同,因为如果没有指定的排序,则可以进行并发评估:旧规则并非如此。例如在:
f (a,b)
previously either a then b, or, b then a. Now, a and b can be evaluated with instructions interleaved or even on different cores.
以前要么 a 然后 b,或者 b 然后 a。现在,a 和 b 可以用交错指令或什至在不同的内核上进行评估。
回答by awiebe
In C99(ISO/IEC 9899:TC3)
which seems absent from this discussion thus far the following steteents are made regarding order of evaluaiton.
在C99(ISO/IEC 9899:TC3)
这似乎从这个讨论迄今以下steteents是关于及其评价问题作了缺席。
[...]the order of evaluation of subexpressions and the order in which side effects take place are both unspecified. (Section 6.5 pp 67)
The order of evaluation of the operands is unspecified. If an attempt is made to modify the result of an assignment operator or to access it after the next sequence point, the behavior[sic] is undefined.(Section 6.5.16 pp 91)
[...] 子表达式的求值顺序和副作用发生的顺序都是未指定的。(第 6.5 节第 67 页)
操作数的计算顺序未指定。如果尝试修改赋值运算符的结果或在下一个序列点之后访问它,则行为 [sic] 未定义。(第 6.5.16 节 pp 91)