重新加载未定义的行为和序列点

时间:2011-01-09 08:40:55

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

将此主题视为以下主题的续篇:

  

上一期
   Undefined behavior and sequence points

让我们重新审视这个有趣的复杂的表达式(斜体短语取自上述主题* smile *):

i += ++i;

我们说这会调用undefined-behavior。我认为,当我这样说时,我们隐含地假设i type 是内置类型之一。

如果i类型是用户定义的类型怎么办?假设它的类型是Index,这在本文后面定义(见下文)。它还会调用未定义的行为吗?

如果是,为什么?它不等同于编写i.operator+=(i.operator++());甚至是语法上更简单的i.add(i.inc());吗?或者,他们是否也调用未定义的行为?

如果不是,为什么不呢?毕竟,对象i在连续的序列点之间被修改两次。请回忆一下经验法则:an expression can modify an object's value only once between consecutive "sequence points。如果i += ++i是表达式,那么它必须调用未定义的行为。如果是这样,那么它的等价物i.operator+=(i.operator++());i.add(i.inc());也必须调用未定义的行为,这似乎是不真实的! (据我所知)

或者,i += ++i不是表达式开头?如果是这样,那么它是什么以及表达式的定义是什么?

如果它是一个表达式,同时它的行为定义良好,那么它意味着与表达式相关的序列点数量某种程度上取决于类型表达式中涉及的操作数。我是否正确(甚至部分)?


顺便问一下,这个表达怎么样?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

您的回复中也必须考虑到这一点(如果您确定了解其行为)。 : - )


++++++i;

在C ++ 03中定义明确吗?毕竟,就是这个,

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};

5 个答案:

答案 0 :(得分:48)

看起来像代码

i.operator+=(i.operator ++());

关于序列点的工作完全正常。 C ++ ISO标准的第1.9.17节对序列点和功能评估进行了说明:

  

当调用函数时(无论函数是否为内联函数),在评估函数体中任何表达式或语句之前发生的所有函数参数(如果有)之后,都会有一个序列点。在复制返回值之后和执行函数之外的任何表达式之前,还有一个序列点。

这表明,例如i.operator ++()作为operator +=的参数在评估后具有序列点。简而言之,因为重载运算符是函数,所以适用正常的排序规则。

很棒的问题,顺便说一下!我真的很喜欢你如何强迫我理解我已经认为我所知道的语言的所有细微差别(并且认为我认为我知道)。 : - )

答案 1 :(得分:12)

http://www.eelis.net/C++/analogliterals.xhtml 我想到了模拟文字

  unsigned int c = ( o-----o
                     |     !
                     !     !
                     !     !
                     o-----o ).area;

  assert( c == (I-----I) * (I-------I) );

  assert( ( o-----o
            |     !
            !     !
            !     !
            !     !
            o-----o ).area == ( o---------o
                                |         !
                                !         !
                                o---------o ).area );

答案 2 :(得分:11)

正如其他人所说,你的i += ++i示例使用用户定义的类型,因为你正在调用函数,而函数包含序列点。

另一方面,假设a[++i] = i是您的基本数组类型,甚至是用户定义的类型,a并不是那么幸运。你在这里遇到的问题是我们不知道首先评估包含i的表达式的哪一部分。可能是++i被评估,传递给operator[](或原始版本)以便在那里检索对象,然后将i的值传递给它(是在我增加之后)。另一方面,可能首先评估后一方,存储以供以后分配,然后评估++i部分。

答案 3 :(得分:8)

我认为它定义明确:

来自C ++草案标准(n1905)§1.9/ 16:

  

“之后还有一个序列点   复制返回值和   在任何执行之前   函数外的表达式13)。   C ++中的几个上下文导致   甚至可以评估函数调用   虽然没有相应的函数调用   语法出现在翻译中   单元。 [示例:评估新的   表达式调用一个或多个   分配和构造函数;   见5.3.4。再举个例子,   调用转换函数   (12.3.2)可能出现在上下文中   没有出现函数调用语法。    - 结束示例]序列指向   函数入口和函数出口(如   如上所述)是的特征   函数调用已评估,无论如何   表达式的语法   调用函数可能是。 “

注意我加粗的部分。这意味着在增量函数调用(i.operator ++())之后但在复合赋值调用(i.operator+=)之前确实存在一个序列点。

答案 4 :(得分:6)

好的。在完成之前的回复之后,我重新考虑了我自己的问题,特别是这一部分,只有诺亚试图answer,但我并不完全相信他。

a[++i] = i;

案例1:

如果a是内置类型的数组。那么诺亚所说的是对的。也就是说,

  

a [++ i] =我不是很幸运   a是你的基本数组类型,或   甚至用户定义了一个。问题   你来到这里是我们不知道的   表达的哪一部分   首先评估包含i的信息。

因此a[++i]=i调用undefined-behavior,或者结果未指定。无论是什么,它都没有明确定义!

PS:在上面的引文中, strike-through 当然是我的。

案例2:

如果a是用户定义类型的对象,它会使operator[]重载,那么又有两种情况。

  1. 如果重载operator[]函数的返回类型是内置类型,则a[++i]=i再次调用undefined-behavior或结果未指定。
  2. 但是如果重载operator[]函数的返回类型是用户定义的类型,那么a[++i] = i的行为是明确定义的(据我所知),因为在这种情况下{{1 }}相当于写a[++i]=i,与a.operator[](++i).operator=(i);相同。也就是说,a[++i].operator=(i);返回的对象会调用作业operator=,这似乎定义得非常明确,因为在a[++i]返回时,{{ 1}}已经被评估过,然后返回对象调用a[++i]函数将++i的更新值作为参数传递给它。 请注意,这两个电话之间存在一个序列点。语法确保这两个调用之间没有竞争,并且operator=将首先被调用,并且连续地,传递给它的参数i也会被首先评估。
  3. 将此视为operator[],其中每个连续的函数调用返回某个用户定义类型的对象。对我来说,这种情况看起来更像是这样:++i,因为在这两种情况下,每次函数调用后都存在序列点。

    如果我错了,请纠正我。 : - )