c++ - 未定义的行为和顺序点

标签 c++ undefined-behavior c++-faq sequence-points

什么是“序列点”?

未定义行为与序列点之间有什么关系?

我经常使用诸如a[++i] = i;之类的有趣且令人费解的表达式来使自己感觉更好。为什么我应该停止使用它们?

如果您已阅读此书,请确保访问后续问题Undefined behavior and sequence points reloaded


(注意:这应该是Stack Overflow's C++ FAQ的条目。如果您想批评以这种形式提供FAQ的想法,那么the posting on meta that started all this就是这样做的地方。 C++ chatroom,这是FAQ想法首先出现的地方,因此,您的答案很可能会被提出该想法的人阅读。)

最佳答案

C ++ 98和C ++ 03

此答案适用于C ++标准的较旧版本。该标准的C ++ 11和C ++ 14版本没有正式包含“序列点”。操作是“先于”或“未排序”或“不确定地排序”。最终效果基本相同,但是术语不同。



免责声明:好的。这个答案有点长。因此阅读时要有耐心。如果您已经知道这些事情,那么再次阅读它们不会使您发疯。

先决条件:C++ Standard的基础知识



什么是序列点?

标准说


  在执行序列中某些特定的点(称为序列点)上,先前评估的所有副作用
  应当完整,以后的评估应没有副作用。 (第1.9 / 7节)


副作用?有什么副作用?

对表达式的求值会产生某些结果,并且如果执行环境的状态另外发生变化,则可以说该表达式(其求值)会产生一些副作用。

例如:

int x = y++; //where y is also an int


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

到目前为止,一切都很好。继续到序列点。 comp.lang.c作者Steve Summit给定的seq点的替代定义:


  顺序点是粉尘沉淀的时间点,可以保证到目前为止已经看到的所有副作用都是完整的。




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

那些是:


在完整表达式(§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是逗号运算符;在func(a,a++),不是逗号运算符,它只是参数aa++之间的分隔符。因此,在这种情况下,行为是不确定的(如果a被认为是原始类型))

在对所有函数参数(如果有)进行求值后,在函数调用(函数是否为内联)时
在函数主体(§1.9/17)中的任何表达式或语句执行之前发生。


1:注意:对完整表达式的评估可以包括对非词法子表达式的评估
完整表达的一部分。例如,与评估默认参数表达式(8.3.6)有关的子表达式被认为是在调用函数的表达式中创建的,而不是在定义默认参数的表达式中创建的

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



什么是未定义行为?

标准在§1.3.12部分中将未定义行为定义为


  行为,例如由于使用错误的程序构造或错误的数据而可能发生的行为,对此本国际标准不施加任何要求3。
  
  在这种情况下,也可能会出现未定义的行为
  国际标准省略了对行为的任何明确定义的描述。


 3:允许的不确定行为,范围从完全忽略具有不可预测结果的情况到在翻译或程序执行过程中以环境特征的书面方式记录的行为(带有或带有-
停止发出诊断消息),终止翻译或执行(发出诊断消息)。

简而言之,未定义的行为意味着从守护程序从鼻子飞出到女友怀孕都可能发生任何事情。



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

在开始讨论之前,您必须了解Undefined Behaviour, Unspecified Behaviour and Implementation Defined Behaviour之间的区别。

您还必须知道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

例如:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.


另一个示例here



现在§5/4中的标准说


1)在上一个序列点与下一个序列点之间,标量对象最多应通过表达式的求值修改其存储值。


这是什么意思?

非正式地,它意味着两个序列点之间的变量不得被多次修改。
在表达式语句中,next sequence point通常在终止分号处,而previous sequence point在上一条语句的末尾。表达式也可以包含中间sequence points

从上面的句子中,以下表达式调用未定义的行为:

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)


但是以下表达式很好:

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的所有访问(在L.H.S和R.H.S中)都直接涉及要写入的值的计算。很好。

该规则将法律表达方式有效地限制为那些在修改之前显然可以进行访问的表达方式。

范例1:

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


范例2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc


之所以不被允许,是因为i的访问之一(a[i]中的访问)与最终存储在i中的值(在i++中发生)无关,因此没有很好的方法定义(无论是对我们还是编译器而言)访问是在存储递增的值之前还是之后进行。因此,行为是不确定的。

例子3:

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




C ++ 11 here的后续答案。

关于c++ - 未定义的行为和顺序点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7267853/

相关文章:

c++ - C中的结构编译器问题

c - C的未指定,未定义和实现定义的行为WIKI

c++ - C++中的结构和类有什么区别?

c++ - Intel HD 3000 上正确的 OpenGL 初始化是什么?

c++ - 未声明错误 Nullptr

c++ - 当您想试验 C++0x 特性时,最好使用什么编译器?

c++ - 没有非空函数的返回语句是否是未定义的行为,其中控制永远不会结束?

c++ - 在 C++ 中,堆分配的对象可以是 const 吗?

c++ - 什么是透明比较器?

c++ - C++ 11中的T &&(双“&”号)是什么意思?