在回答this question之后,对于有问题的代码是否是未定义的行为进行了长时间的讨论。这是代码:
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"]
不存在,则有人认为它将被分配两次,而两者之间没有排序。我个人没有看到如果我认为真实的两件事实际上是如何发生的那样:
当选择一个侧面进行评估时,必须在另一侧开始之前对整个侧面进行评估。
诸如word_count [“a”] = 1之类的构造表现出明确定义的行为,即使在插入元素然后分配给它的情况下也是如此。
这两个陈述是真的吗?最终,实际上是未定义的行为,如果是,为什么第二个语句有效(假设确实如此)?如果第二个是假的,我相信世界上所有的myMap[i]++;
都是不正确的。
答案 0 :(得分:5)
行为未指定,但未定义。
注意,在表达式中:
word_count["a"] = word_count.count("a") == 0 ? 1 : 2;
// ^
标有^
的赋值运算符是内置赋值运算符,因为std::map
的{{1}}返回operator []
。< / p>
关于内置赋值运算符的C ++ 11标准的第5.17 / 1段:
赋值运算符(=)和复合赋值运算符从右到左分组。 [..] 在所有情况下,赋值都在值之后排序 计算右和左操作数,并在赋值表达式的值计算之前。 对于不确定顺序的函数调用,复合赋值的操作是 一次评估。
这意味着在内置作业中,例如:
size_t&
首先评估操作数(以未指定的顺序),然后执行赋值,最后执行整个赋值表达式的值计算。
考虑原始表达:
a = b
由于上面引用的段落,在任何情况下都不会对同一个对象进行两次无序分配:标记为word_count["a"] = word_count.count("a") == 0 ? 1 : 2;
// ^
的作业将在执行分配后始终按排序如果地图中不存在关键字^
,则按operator []
(作为左侧表达式评估的一部分)。
但是,根据首先评估分配的哪一方,表达式将具有不同的结果。因此,行为未指定,但未定义。
答案 1 :(得分:2)
未指明,但未定义。
word_count.operator[]("a")
和word_count.count("a")
是函数调用。函数执行由标准保证不交错 - 首先在第二个之前完全排序或反过来。
具体定义可能因标准而异,在C ++ 11相关条款中为1.9 / 15:
调用函数中的每个评估(包括其他函数 在之前或之后没有特别排序的调用) 被调用函数体的执行是不确定的 关于被调用函数的执行顺序。 9
9)换句话说,函数执行不会相互交错。
不确定顺序在1.9 / 13中定义:
当A为A时,评估A和B是不确定的 在B或B在A之前测序之前测序,但未指定 这
例如,评估:
word_count["a"] = word_count.count("a");
由三部分组成:
word_count.operator[]("a")
word_count.count("a")
让<
表示'在之前排序'。标准的引用部分保证1 < 2
或2 < 1
。在@Andy Prowl中引用的部分答案也显示了1 < 3
和2 < 3
。所以,只有两个选择:
1 < 2 < 3
2 < 1 < 3
在这两种情况下,一切都完全排序,UB没有机会。