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

Naw*_*waz 84 c++ c++-faq undefined-behavior sequence-points

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

上一部分
未定义的行为和序列点

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

i += ++i;
Run Code Online (Sandbox Code Playgroud)

我们说这会调用undefined-behavior.我假定说这个的时候,我们隐含假设i是内置的类型之一.

如果什么类型i是用户定义类型?比如它的类型是Index在本文后面定义的(见下文).它还会调用未定义的行为吗?

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

如果不是,为什么不呢?毕竟,对象在连续的序列点之间i被修改两次.请回想一下经验法则:表达式只能在连续的"序列点"之间修改一个对象的值.如果 i += ++i是表达式,那么它必须调用未定义的行为.如果是,那么它的等价物i.operator+=(i.operator++());i.add(i.inc());必须调用undefined-behavior似乎是不真实的!(据我所知)

或者,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.
Run Code Online (Sandbox Code Playgroud)

你必须在你的回答中考虑这一点(如果你肯定知道它的行为).:-)


++++++i;
Run Code Online (Sandbox Code Playgroud)

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

((i.operator++()).operator++()).operator++();
Run Code Online (Sandbox Code Playgroud)
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;
        }
};
Run Code Online (Sandbox Code Playgroud)

tem*_*def 48

它看起来像代码

i.operator+=(i.operator ++());
Run Code Online (Sandbox Code Playgroud)

关于序列点,完美地工作.C++ ISO标准的1.9.17部分对序列点和功能评估进行了说明:

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

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

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


Ind*_*ant 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 );
Run Code Online (Sandbox Code Playgroud)


Edw*_*nge 11

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

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


Mat*_*hen 8

我认为这是明确定义的:

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

"在复制返回值之后和执行函数13之外的任何表达式之前,还有一个序列点.C++中的几个上下文导致函数调用的评估,即使在翻译单元中没有出现相应的函数调用语法. [ 实施例:新的表达式的求值调用一个或多个分配和构造函数;见5.3.4对于另一个例子,一个转换功能(12.3.2)的调用可以在其中出现没有函数调用的语法背景中出现的- .端示例 ]函数入口和函数出口的序列点(如上所述)是被评估的函数调用的特征,无论调用函数的表达式的语法如何.

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


Naw*_*waz 6

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

a[++i] = i;
Run Code Online (Sandbox Code Playgroud)

情况1:

If a是内置类型的数组.那么诺亚所说的是对的.那是,

a [++ i] =我不是很幸运,假设a是你的基本数组类型, 甚至是用户定义的 .你在这里遇到的问题是我们不知道首先评估包含i的表达式的哪一部分.

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

PS:在上面的报价中, 当然是我的.

案例2:

如果a是超载的用户定义类型的对象operator[],则有两种情况.

  1. 如果重载operator[]函数的返回类型是内置类型,则再次a[++i]=i调用undefined-behavior或结果未指定.
  2. 但是如果重载operator[]函数的返回类型是用户定义的类型,那么行为a[++i] = i是明确定义的(据我所知),因为在这种情况下a[++i]=i等同于写入a.operator[](++i).operator=(i);与...相同a[++i].operator=(i);.也就是说,operator=返回的对象上调用赋值a[++i],这似乎是非常明确的,因为时间a[++i]返回++i已经被计算,然后返回的对象调用operator=函数将更新后的值i作为参数传递给它.请注意,这两个调用之间存在一个序列点.并且语法确保这两个调用之间没有竞争,并且operator[]将首先被调用,并且连续地,++i传递给它的参数也将首先被评估.

可以想象这是someInstance.Fun(++k).Gun(10).Sun(k).Tun();每个连续函数调用返回某个用户定义类型的对象.对我来说,这种情况看起来更像是:eat(++k);drink(10);sleep(k)因为在这两种情况下,每次函数调用后都存在序列点.

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

  • @Philip:根本没有意义.在Fun()和Sun()之间存在一个序列点,但它们之间的参数不存在序列点.它就像说,在'eat()`和`sleep()之间存在序列点(s),但在它们之间甚至没有参数.如何通过序列点分隔的两个函数调用的参数属于*相同的*序列点? (3认同)