【问题标题】:Is this code behavior defined?此代码行为是否已定义?
【发布时间】:2011-07-25 05:36:34
【问题描述】:

以下代码在控制台打印什么?

map<int,int> m;
m[0] = m.size();
printf("%d", m[0]);

可能的答案:

  1. 未定义代码的行为,因为未定义编译器首先执行的语句m[0]m.size()。所以它可以打印10
  2. 它打印0,因为赋值运算符的右侧首先执行。
  3. 它打印1,因为operator[] 具有完整语句m[0] = m.size() 的最高优先级。因此,会发生以下一系列事件:

    • m[0] 在地图中创建一个新元素
    • m.size() 被调用,现在是 1
    • m[0] 被分配了先前返回的(通过 m.size())1
  4. 真正的答案?,这是我不知道的^^

【问题讨论】:

  • 嗯,它当然与[] 运算符的优先级无关,因为这与这里无关。除此之外,我不知道答案。好问题。
  • +1 提出一个非常好的问题
  • 与大多数这些问题一样,您为什么还要这样做?
  • @jk:出于好奇。了解语言的限制是很好的,这样您就可以在实际代码中很好地避开它们。

标签: c++ operators


【解决方案1】:

在此语句中调用operator [] 和调用clear 之间没有sequence point。因此,行为应该是未定义的。

【讨论】:

  • 语句中至少有4个序列点,在函数调用size()operator[]的开头和结尾各一个。
  • @Steve true 但它们与等号前后语句的相对评估顺序无关。
  • 有趣。您链接到的文章确实说:“(在 C++ 中,重载运算符的作用类似于函数,因此已重载的运算符以与函数调用相同的方式引入序列点。)”。这将使 [] 运算符成为序列点。
  • @Arkadiy 查看史蒂夫评论的答案。
【解决方案2】:

确实打印 1,并且没有使用 gcc 引发警告(!)。它应该发出警告,因为它是未定义的。

operator[]operator. 的优先级均为 2,而 operator= 的优先级为 16。
这意味着明确定义m[0]m.size() 将在赋值之前执行。但是,并没有定义哪个先执行。

【讨论】:

  • 如果operator[]operator.的优先级不一样会不会不一样?
  • @Woltan:不,只有两者的优先级都高于operator= 才有意义,以便在赋值之前对两个子表达式进行评估(以未指定的顺序)。如果其中任何一个的优先级低于operator=,那么表达式无论如何都会有不同的含义(如果它仍然是格式正确的)。
  • @Woltan:没有。优先级用于计算如何添加括号来消除表达式的歧义。所以m[0] = m.size() 等价于(m[0]) = (m.size()),而not 例如(m[0] = m).size(),因为[]. 绑定比= 更紧密。但是优先级并不能确定在整个表达式中计算子表达式的顺序 - 优先级 2 运算符可以在优先级 1 运算符之前计算,前提是两者都不使用另一个运算符的结果。
  • 首先,它是未指定的,不是未定义的。并且编译器看不到任何东西可以让它知道它是未指定的,所以编译器不能抱怨。而且C++没有优先类,反正这里的优先级是无关紧要的。
【解决方案3】:

我相信m[0] 中是否存储0 或1 是未指定,但这不是未定义的行为。

LHS 和 RHS 可以按任意顺序出现,但它们都是函数调用,因此它们在开始和结束时都有一个序列点。他们两个共同访问同一个对象而没有插入序列点的危险。

赋值是实际的 int 赋值,而不是关联序列点的函数调用,因为 operator[] 返回 T&amp;。这有点令人担忧,但它不会修改在此语句中的其他任何地方访问的对象,所以这也是安全的。它是在operator[] 中访问的,当然是在它被初始化的地方,但是这发生在从operator[] 返回的序列点之前,所以没关系。如果不是,m[0] = 0; 也将是未定义的!

但是,operator= 的操作数的求值顺序并没有被标准指定,因此调用 size() 的实际结果可能是 0 或 1,具体取决于发生的顺序。

不过,以下将是未定义的行为。它不进行函数调用,因此没有什么可以阻止 size 在没有中间序列点的情况下被访问(在 RHS 上)和修改(在 LHS 上):

int values[1];
int size = 0;

(++size, values[0] = 0) = size;
/*     fake m[0]     */  /* fake m.size() */

【讨论】:

    【解决方案4】:

    鉴于 C++17 几乎已经出现,我认为值得一提的是,这段代码现在在新标准下表现出明确定义的行为。对于这种= 是对整数的内置赋值的情况:

    [expr.ass]/1:

    赋值运算符 (=) 和复合赋值运算符 all 从右到左分组。都需要一个可修改的左值作为左值 操作数并返回一个引用左操作数的左值。结果 如果左操作数是位域,则在所有情况下都是位域。在所有 情况下,赋值是在值计算之后排序的 右操作数和左操作数,以及在计算值之前 赋值表达式。 右操作数在左操作数之前排序 操作数。 对于不确定顺序的函数调用, 复合赋值的操作是单一的求值。

    这让我们只有一个选择,那就是#2。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-16
      • 1970-01-01
      • 2020-02-25
      • 1970-01-01
      • 1970-01-01
      • 2010-11-14
      • 1970-01-01
      相关资源
      最近更新 更多