未定义的行为和序列点 [英] Undefined behavior and sequence points

查看:111
本文介绍了未定义的行为和序列点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

什么是序列点?



未定义的行为和序列点之间的关系是什么?



我经常使用有趣和令人费解的表达式,如 a [++ i] = i; ,使自己感觉更好。为什么我要停止使用它们?



如果您已阅读此文件,请务必访问后续问题 重新载入未定义的行为和序列点




注意:这意味着要输入 Stack Overflow的C ++常见问题。如果您想批评提供此表单中的常见问题解答,然后在开始的元上发布帖子这个问题的答案会在 C ++聊天室中进行监控,其中常见问题的想法开始在第一位,所以你的答案很有可能被提出想法的人阅读。)

解决方案

C ++ 98和C ++ 03



的C ++标准。标准的C ++ 11和C ++ 14版本不正式包含序列点;操作是之前排序或未排序或不确定排序。

免责声明:好的。这个答案有点长。所以在阅读它时有耐心。如果你已经知道这些东西,再次阅读它们不会让你疯狂。



前提条件:基本知识 C ++标准






什么是序列点?



标准表示


在执行序列中的某些指定点称为序列点 ,先前评估的所有副作用
应当是完整的,并且不会发生后续评估的副作用。 (§1.9/ 7)




副作用?什么是副作用?



表达式的求值产生某些结果,如果执行环境的状态有变化,则表达式)有一些副作用。



例如:

  int x = y ++; //其中y也是一个int 

除了初始化操作之外, y 由于 ++ 运算符的副作用而更改。



到目前为止很好。继续到序列点。由comp.lang.c作者提供的seq-points的替换定义:


序列点是灰尘已经沉降的时间点,并且已经看到的所有副作用都保证完成。







C ++标准中列出的常见序列点是什么?



>


  • 在完整表达式的计算结束时(§1.9/ 16 表达式是不是另一个表达式的子表达式的表达式。) 1



p>

  int a = 5; //;是这里的序列点




  • 第一个表达式(§1.9/ 18 2

    的评估之后的以下表达式


    • a&& b(§5.14)

    • a || b(§5.15)

    • a? b:c(§5.16)

    • a,b(§5.18) $ c> func(a,a ++) 不是逗号运算符,它只是参数之间的分隔符 a a ++ 。在这种情况下,行为是未定义的,如果 a 原始类型)


  • 在函数调用(无论函数是否为内联函数)在函数体中执行任何表达式或语句(§1.9/ 17 )之前发生
    的所有函数参数(如果有)。

    / li>


1:注意:对全表达式的求值可以包括对不是词法的子表达式的求值
全表达式的一部分。例如,在计算默认参数表达式(8.3.6)时涉及的子表达式被认为是在调用函数的表达式中创建,而不是定义默认参数的表达式



2:指定的运算符是内置运算符,如第5节所述。当这些运算符之一在有效上下文中重载(第13条)时,指定用户定义的运算符函数,表达式指定函数调用,而操作数形成参数列表,而在它们之间没有隐含的序列点。






什么是未定义的行为?



标准定义了§1.3.12 p>


行为,例如在使用错误程序结构或错误数据时可能出现的行为,本国际标准强加无要求 3




国际标准省略对行为的任何明确定义的描述时,也可以预期未定义的行为。


3:允许未定义的行为范围包括完全忽略具有不可预测的结果的情况,在翻译或程序执行期间(用发出诊断消息时带有或带有
),以终止翻译或执行(发出诊断消息)。



总之,未定义的行为意味着任何可能发生于从你的鼻子飞到你的女朋友怀孕的守护进程。






未定义的行为和序列点之间的关系是什么?



在我开始之前,你必须知道差异未定义的行为,未指定的行为和实现定义的行为。 p>

您还必须知道个别运算符的操作数和单个表达式的子表达式的求值顺序以及副作用发生的顺序是未指定



例如:

  int x = 5,y = 6; 

int z = x ++ + y ++; //未指定是否首先评估x ++或y ++。

另一个例子


$ b


$ b

现在标准§5/ 4




  • 在前一个和下一个序列点之间,标量对象的存储值最多只能通过表达式的求值修改一次。



这是什么意思?



非正式地意味着在两个序列点之间,一个变量不能被修改多于一次。
在表达式语句中,下一个序列点通常在终止分号,而前一个序列点在上一条语句的结尾。一个表达式也可以包含中间序列点



从上面的句子中,下面的表达式调用未定义的行为。 p>

  i ++ * ++ i // i被修改多次
i = ++ i //与上面相同
++ i = 2; //同上面
i = ++ i +1 //与上面一样
++++++ i; // parsed as(++(++(++ i)))

i =(i,++ i,++ i) //未定义行为,因为在'++ i`(最右边)和赋值给'i'之间没有序列点(`i`被修改多于一次b / w两个SP)

但以下表达式很好

  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)此外,只能访问先前值以确定要存储的值。



这是什么意思?这意味着如果一个对象被写入一个完整的表达式内,在同一个表达式内对它的任何和所有访问都必须直接参与要写入的值的计算。



例如在 i = i + 1 所有访问 i (在LHS和RHS中)直接参与要写入的值的计算



这条规则有效地将法律表达式限制在那些修改之前可以访问的地方。



示例1:

  std :: printf(%d%d,i,++ i) //由于规则2调用未定义的行为

示例2:

  a [i] = i ++ //或a [++ i] = i或a [i ++] = ++ i等

c $ c> a [i] )与最终存储在i中的值无关(在 i ++ 中发生) ,因此没有好的方法来定义 - 无论是对于我们的理解还是编译器 - 访问是否应该在增加的值被存储之前或之后。因此,此行为未定义。



示例3:

  int x = i + i ++; //类似于上面的






后续解决此处


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?

If you've read this, be sure to visit the follow-up question 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 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.)

解决方案

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 : 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


What are Sequence Points?

The Standard says

At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place. (§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.

So far so good. Moving on to sequence points. An alternation definition of seq-points given by the comp.lang.c author 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 ?

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.)1

Example :

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) 2

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18) (in func(a,a++) , is not a comma operator, it's merely a separator between the arguments a and a++. The behaviour is undefined in that case if a 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 as

behaviour, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements 3.

Undefined behaviour may also be expected when this International Standard omits the description of any explicit definition of behavior.

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).

In short, undefined behaviour means anything can 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.

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:

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

  • 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.

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.

From the above sentence the following expressions invoke Undefined Behaviour.

i++ * ++i; // i is modified more than once
i = ++i    // same as above
++i = 2;   // same as above
i = ++i +1 // same as above
++++++i;   //parsed as (++(++(++i)))

i = (i,++i,++i); // Undefined Behaviour because there's no sequence point between `++i`(right most) and assignment to `i` (`i` gets modified more than once b/w two SP)

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.

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 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:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Example 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.

Example 3 :

int x = i + i++ ;// Similar to above


Follow up answer here.

这篇关于未定义的行为和序列点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆