4

在回答了这个问题之后,关于有问题的代码是否是未定义的行为进行了长时间的讨论。这是代码:

std::map<string, size_t> word_count;
word_count["a"] = word_count.count("a") == 0 ? 1 : 2;

首先,这是公认的,这至少是未指定的。结果根据首先评估作业的哪一侧而有所不同。在我的回答中,我跟踪了四个结果案例中的每一个,其中首先评估了哪一方的因素以及该元素在此之前是否存在。

还出现了一个简短的表格:

(x = 0) = (x == 0) ? 1 : 2; //started as
(x = 0) = (y == "a") ? 1 : 2; //changed to

我声称它更像这样:

(x = 0, x) = (x == 0) ? 1 : 2; //comma sequences x, like [] should

最终,我找到了一个似乎对我有用的例子:

i = (++i,i++,i); //well-defined per SO:Undefined Behaviour and Sequence Points

回到原来的,我把它分解成相关的函数调用,以便更容易理解:

operator=(word_count.operator[]("a"), word_count.count("a") == 0 ? 1 : 2);
   ^       inserts element^                        ^reads same element
   |
assigns to element

如果word_count["a"]不存在,则有人认为它将被分配两次,而两者之间没有排序。如果我认为是真的两件事实际上是:

  1. When a side is picked to be evaluated, the whole side has to be evaluated before the other side can start.

  2. 诸如 word_count["a"] = 1 之类的结构表现出明确定义的行为,即使在插入元素然后分配给它的情况下也是如此。

这两个说法是真的吗?最终,这实际上是未定义的行为吗?如果是,为什么第二个语句起作用(假设它起作用)?如果第二个是假的,我相信myMap[i]++;世界上所有的 s 都是不正确的。

有用的链接:未定义的行为和序列点

4

2 回答 2

5

行为是未指定的,但不是 undefined

请注意,在表达式中:

word_count["a"] = word_count.count("a") == 0 ? 1 : 2;
//              ^

用 标记的赋值运算符^内置的赋值运算符,因为std::map'operator []返回 a size_t&

根据 C++11 标准关于内置赋值运算符的第 5.17/1 段:

赋值运算符 (=) 和复合赋值运算符都从右到左分组。[..]在所有情况下,赋值都是在左右操作数的值计算之后和赋值表达式的值计算之前进行排序的。对于不确定顺序的函数调用,复合赋值的操作是单次求值。

这意味着在内置分配中,例如:

a = b

首先计算操作数(按未指定的顺序),然​​后执行赋值,最后执行整个赋值表达式的值计算。

考虑原始表达式:

word_count["a"] = word_count.count("a") == 0 ? 1 : 2;
//              ^

由于上面引用的段落,在任何情况下都不会对同一个对象进行两个未排序的赋值:标记为的赋值^将始终在由operator [](作为左侧表达式评估的一部分)执行的赋值之后"a"排序,以防键地图中不存在。

但是,根据首先评估分配的哪一侧,表达式将具有不同的结果。因此,行为是未指定的,但不是未定义的。

于 2013-04-07T18:38:34.803 回答
2

它是未指定的,但不是未定义的。

word_count.operator[]("a")并且word_count.count("a")是函数调用。标准保证函数执行不会交错 - 第一个在第二个之前完全排序,或者相反。

具体定义可能因标准而异,在 C++11 中的相关条款在 1.9/15 中:

调用函数(包括其他函数调用)中的每个求值,如果在被调用函数的主体执行之前或之后没有特别排序,则相对于被调用函数的执行是不确定的。9

9) 换句话说,函数执行不会相互交错。

不确定排序在 1.9/13 中定义:

当 A 在 B 之前排序或 B 在 A 之前排序时,评估 A 和 B 的排序不确定,但未指定哪个。

例如,评估:

word_count["a"] = word_count.count("a");

由三部分组成:

  1. 执行word_count.operator[]("a")
  2. 执行word_count.count("a")
  3. 任务

<意思是“之前排序”。引用的部分标准保证要么1 < 2要么2 < 1. @Andy Prowl 回答中引用的部分也表明1 < 32 < 3。所以,只有两种选择:

  • 1 < 2 < 3
  • 2 < 1 < 3

在这两种情况下,一切都已完全排序,UB 没有机会。

于 2013-04-07T18:30:51.583 回答