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/27089283/

相关文章:

c++ - 为什么结构的 sizeof 不等于每个成员的 sizeof 之和?

c++ - 将结构更改为类(以及其他类型更改)和 ABI/代码生成

c++ - 如何删除一个空的对象数组?

c++ - 分配过大的堆栈结构是未定义的行为吗?

c++ - 使用 char 参数从 <cctype> 调用函数是否安全?

*scanf 中 %n 运算符的一致性和行为

c++ - 什么是 undefined reference /未解析的外部符号错误,我该如何解决?

c++ - 任何不将大型开关 block 转换为二叉树的编译器?

c++ - 在 C 和 C++ 中,术语 "empty loop"到底指的是什么?

c++ - 什么是 undefined reference /未解析的外部符号错误,我该如何解决?