2

这是示例代码:

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 上的未定义行为。为了简化这篇文章,计数不是静态的,但它不会改变任何东西。我收到的错误如下:

test.cpp:8:44:警告:对“计数”的多个未排序修改 [-Wunsequenced]

4

3 回答 3

10

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

从历史上看,这些子句可能是未排序的,因此对 的两个未排序的修改count会给出未定义的行为。

(尽管,正如评论中所指出的,即使在那时它也可能已经很好地定义了——您可能可以将标准解释为暗示每个子句都是一个完整的表达式,并且每个完整表达式的末尾都有一个序列点。我将把它留给历史学家来讨论过时语言的细节。)

于 2013-11-08T14:59:13.097 回答
2

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

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


编辑:感谢@MikeSeymour:这是C++03以前的问题。似乎在 中,为这种情况定义C++11了评估顺序。

于 2013-11-08T14:56:13.220 回答
2

更新 2

因此,经过一些研究,我意识到这实际上是很好定义的,尽管评估顺序是未指定的。将各个部分放在一起非常有趣,尽管对于C++11 案例有一个更一般的问题,但没有涵盖C ++11之前案例的一般问题,所以我最终创建了一个自我回答问题,初始化程序中同一变量的多个突变是否列出了涵盖所有细节的 C++11 之前的未定义行为。

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

更新

正如 James 指出的那样,它可能不是未定义的C++11之前的版本,它似乎依赖于将每个元素的初始化解释为一个完整的表达式,但目前尚不清楚您是否可以肯定地做出这样的声明。

原来的

C++11之前,在一个序列点内多次修改变量是未定义的行为,我们可以看到,通过查看旧标准草案中的相关部分将是5 表达式4节,它说(强调我的):

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

C++11 草案标准中,这发生了变化,并从1.9 程序执行15段中的以下措辞说(强调我的):

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

我们可以看到,对于8.5.4 List-initialization4节中的初始化列表来说:

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

于 2013-11-08T15:08:56.480 回答