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

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

什么是“序列点”?

undefined 行为与序列点之间有什么关系?

我经常使用诸如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的基础知识

什么是序列点?

标准说

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)



副作用?有什么副作用?

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

例如:
int x = y++; //where y is also an int

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

到现在为止还挺好。继续到序列点。 comp.lang.c作者Steve Summit给出的seq点的替代定义:

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.



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节),从而指定了用户定义的运算符函数时,该表达式指定函数调用和操作数形成一个参数列表,它们之间没有隐含的序列点。

    什么是 undefined 行为?

    标准在§1.3.12节中将 undefined 行为定义为

    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:允许的不确定行为,范围从完全忽略具有无法预测结果的情况到在翻译或程序执行过程中以环境特征记录的方式表现(带有或带有-
    停止发出诊断消息),终止翻译或执行(发出诊断消息)。

    简而言之, undefined 的行为意味着可能会发生,从从您的 Nose 中飞出的守护程序到您的女友怀孕都可能发生。

    undefined 行为和序列点之间有什么关系?

    在开始讨论之前,您必须了解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

    从上面的句子中,以下表达式调用 undefined 的行为:
    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/63104143/

    相关文章:

    c++ - 什么是 undefined reference /未解析的外部符号错误以及如何修复它?

    c++ - 内存分配/解除分配?

    c++ - 由于C++中的表导致编译错误

    c++ - 在 CString 中查找以空格分隔的单词的最佳方法

    c++ - 为什么需要在列表拼接功能cpp中使用列表参数

    c++ - 在 C 和 C++ 中,使用逗号运算符(如 "a = b,++a;")的表达式是否未定义?

    c++ - 调用从先前执行中保存的函数指针如何失败?

    c++ - 何时在空实例上调用成员函数会导致未定义的行为?

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

    c++ - 为什么 "using namespace std;"被认为是不好的做法?