【问题标题】:std::map operator [] -- undefined behaviour?std::map operator [] -- 未定义的行为?
【发布时间】:2016-10-03 13:25:48
【问题描述】:

我刚刚提出了以下问题:这是否会导致未定义的行为,为什么?

std::map<int, int> m;
m[10] += 1;

它可以完美地编译和运行,但并不能证明什么。 它类似于常见的 UB 示例 i = ++i + i++;,因为 operator[] 确实有副作用,但另一方面,假设任何评估顺序(从左到右和从右到左)都会让我进入地图的相同最终状态

附:可能相关:http://en.cppreference.com/w/cpp/language/eval_order

编辑

对不起,我应该写的

 m[10] = m[10] + 1;

【问题讨论】:

  • 这样的问题确实表明语言变得过于复杂了
  • 这怎么可能是UB
  • @Destructor,请看编辑,有什么变化吗?
  • @Slava 答案很简单……operator[] 默认在键 10 处构造一个映射值,并将其加一,正如人们所期望的那样。我不明白这有多复杂
  • 如果 m[10] = m[10] + 1; 之类的行为未定义,则 [ ] 严重损坏。

标签: c++ dictionary stl undefined-behavior stdmap


【解决方案1】:

这没有什么不确定的。 operator[] 返回一个对映射条目的左值引用(它在必要时创建)。然后,您只是增加这个左值表达式,即基础条目。

评估顺序规则规定,对于修改分配操作,副作用在评估左侧(即映射条目的左值引用)和右侧(即常量1)操作数之后严格排序。这个例子完全没有歧义。

更新:在您更新的示例中,没有任何变化。再次,修改m[10] 的副作用严格排序在其他操作之后(即在右侧评估为左值,在右侧评估它,然后执行加法)。

相关排序规则,来自cppreference

8) 内置的副作用(左参数的修改) 赋值运算符和所有内置的复合赋值运算符 在值计算(但不是副作用)之后排序 左右参数,并在值之前排序 赋值表达式的计算(即在返回之前 对修改对象的引用)

【讨论】:

  • 可能我误导性地使用了广义意义上的“副作用”短语,我特别担心容器结构的副作用:左侧部分必须以某种方式评估(即我们想要改变的左值)。这个评估是一个代价高昂的操作,因为我们在映射中插入了一个新值。想象一下,这会迫使底层搜索树彻底重新平衡。此外,正如您所友好地回答的那样,必须评估右侧部分。您引用的规则(正如我所看到的其他规则)没有说明价值计算顺序
  • 因此,根据引用的页面,这些树结构更改可以重叠:“A 和 B 的评估是无序的:它们可以按任何顺序执行并且可能重叠(在单个执行线程内,编译器可以交错包含 A 和 B) 的 CPU 指令"。这反过来又使行为未定义
  • “它们可以按任何顺序执行并且可能重叠” - 实际上严格来说这不是真的。它们确实可以按任何顺序执行,但是因为在这种情况下它们是函数调用(对重载的operator[]),所以它们可能不会重叠(在同一页的其他地方有说明)。因此,它们只是不确定排序而不是未排序,因此不符合未定义行为的标准。
【解决方案2】:

我不太确定你的担心是什么(如果这个答案不够充分,也许你应该澄清你的问题),但m[10] += 1; 没有被翻译成m[10] = m[10] + 1;,因为m 是用户定义的类编译器永远不会翻译类型和重载运算符。对于具有用户定义类类型的 ab 对象:

  • a+=b 并不意味着 a = a + b(除非你这样做)
  • a!=b 不代表 !(a==b)(除非你这样做)

此外,函数调用永远不会重复。

所以m[10] += 1; 表示调用重载operator[] 一次;返回类型是一个引用,所以表达式是一个左值;然后将内置运算符+= 应用于左值。

没有评估问题的顺序。甚至没有多个可能的评估顺序!

另外,您需要记住 std::map&lt;&gt;::operator[] 的行为不像 std::vector&lt;&gt;::operator[](或 std::deque's),因为 map 是完全不同的抽象:vectordeque 是序列概念的实现(位置很重要),但map 是一个关联容器(“关键”很重要,而不是位置):

  • std::vector&lt;&gt;::operator[] 采用数字索引,如果该索引不引用向量的元素,则没有意义。
  • std::map&lt;&gt;::operator[] 接受一个键(可以是任何满足基本约束的类型),如果不存在(键,值)对,则创建一个(键,值)对

请注意,出于这个原因,std::map&lt;&gt;::operator[] 本质上是一个修改操作,因此是非 const,而 std::vector&lt;&gt;::operator[] 本质上不是修改,但可以通过返回的引用进行修改,因此是“传递地” const:如果v 是非常量向量,则v[i] 将是一个可修改的左值,如果v 是一个常量向量,则将是一个常量左值。

所以不用担心,代码具有完美定义的行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-27
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 2020-10-13
    • 2021-04-20
    • 2022-08-24
    相关资源
    最近更新 更多