首页 文章

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

提问于
浏览
82

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

上一期安装未定义的行为和序列点

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

i += ++i;

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

如果 i 的类型是用户定义的类型怎么办?比如它的类型是 Index ,这篇文章稍后会定义(见下文) . 它还会调用未定义的行为吗?

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

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

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

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


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

//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 回答

  • 6

    它看起来像代码

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

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

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

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

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

  • 11

    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 );
    
  • 8

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

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

  • 48

    我认为这是明确定义的:

    根据C草案标准(n1905)§1.9/ 16:

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

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

  • 12

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

    a[++i] = i;
    

    案例1:

    如果 a 是内置类型的数组 . 然后诺亚所说的是正确的 . 那是,

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

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

    PS:在上面的引文中,罢工当然是我的 .

    案例2:

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

    • 如果重载的 operator[] 函数的返回类型是内置类型,则 a[++i]=i 再次调用undefined-behavior或结果未指定 .

    • 但是如果重载的 operator[] 函数的返回类型是用户定义的类型,那么 a[++i] = i 的行为是明确定义的(据我所知),因为在这种情况下 a[++i]=i 等同于写 a.operator[](++i).operator=(i); ,它与 a[++i].operator=(i); 相同 . 也就是说,在 a[++i]returned 对象上调用赋值 operator= ,这似乎是非常明确的,因为在 a[++i] 返回时, ++i 已经被评估,然后返回的对象调用 operator= 函数将 i 的更新值传递给它作为论据 . Note that there is a sequence point between these two calls . 并且语法确保这两个调用之间没有竞争,并且首先调用 operator[] ,并且连续地,传递给它的参数 ++i 也将首先被评估 .

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

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

相关问题