为什么这种未定义的行为?

时间:2013-11-08 14:52:48

标签: c++ clang undefined-behavior

以下是示例代码:

X * makeX(int index) { return new X(index); }
struct Tmp {
    mutable int count;
    Tmp() : count(0) {}
    const X ** getX() const { 
        static const X* x[] = { makeX(count++), makeX(count++) };
        return x; 
    }
};

这将在静态数组构造中报告CLang build 500上的Undefined Behavior。 为了简化这篇文章,计数不是静态的,但它不会改变任何东西。我收到的错误如下:

  

test.cpp:8:44:警告:对'count'进行多次无序修改[-Wunsequenced]

3 个答案:

答案 0 :(得分:10)

在C ++ 11中,这很好;初始化列表的每个子句在下一个列表之前排序,因此评估是明确定义的。

从历史上看,这些条款可能没有排序,因此count的两个未经测序的修改会给出未定义的行为。

(虽然,正如评论中所指出的那样,它可能已经定义得很好 - 你可以将标准理解为暗示每个子句都是完整表达式,并且在每个完整表达的最后都有一个序列点。我将把它留给历史学家来讨论过时语言的细节。)

答案 1 :(得分:2)

因为在这种情况下,,不是序列点,但在数组元素的初始化中更像是分隔符。

换句话说,您在没有序列点的语句中(修改之间)修改相同的变量两次。


编辑:感谢@MikeSeymour:这是C++03之前的一个问题。似乎在C++11中,对于这种情况,评估的顺序是

答案 2 :(得分:2)

更新2

因此,经过一些研究后,我意识到这实际上是定义良好的,尽管评估顺序是未指定的。将各个部分组合在一起是非常有趣的,尽管有一个更普遍的问题涵盖了the C++11 case,但是没有关于前 C ++ 11 案例的一般性问题所以我最终创建一个涵盖所有细节的自我回答问题Are multiple mutations of the same variable within initializer lists undefined behavior pre C++11

基本上,看到makeX(count++), makeX(count++)时的本能是将整个事物看作是一个完整的表达式,但事实并非如此,因此每个初始化器都有一个序列点

更新

正如詹姆斯指出的那样,它可能不是 undefined pre- C ++ 11 ,这似乎依赖于将每个元素的初始化解释为完整的表达但不清楚你肯定可以提出这种说法。

原始

C ++ 11 在序列点内多次修改变量undefined behavior,我们可以通过查看{{3}中的相关部分来看到将部分5 表达式 4 表示(强调我的):

  

[...] 在上一个和下一个序列点之间,标量对象的表达式评估最多只能修改一次的存储值。此外,只能访问先前值以确定要存储的值。对于完整表达式的子表达式的每个允许排序,应满足本段的要求; 否则行为未定义。

C ++ 11 older draft standard中,此更改以及1.9 部分执行 15 <中的以下措辞/ em>说(强调我的):

  

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的评估是不确定的。 [...] 如果对标量对象的副作用相对于同一标量对象的另一个副作用或使用相同标量对象的值进行的值计算未被排序,则行为未定义。

我们可以看到,对于8.5.4 列表初始化 4 中的初始化列表,说:

  

在braced-init-list的initializer-list中,initializer-clause(包括pack扩展(14.5.3)产生的任何结果)按照它们出现的顺序进行评估。也就是说,与给定的initializer子句相关联的每个值计算和副作用在每个值计算和副作用之前都会在与初始化列表的逗号分隔列表中的任何initializer子句相关联之前进行排序。