初始化列表中出现多个变异是否属于未定义行为?

22

我对初始化列表和序列点很好奇。我之前读到过初始化列表中的评估顺序是从左到右的。如果确实如此,那么在评估点之间必须存在某种序列点,我错了吗?因此,根据上述内容,以下代码是否有效?是否存在任何导致未定义行为的内容?

int i = 0;

struct S {
    S(...) {} 
    operator int() { return i; }
};

int main() {
    i = S{++i, ++i};
}

非常感谢任何和所有的回复。

2个回答

20

是的,代码是有效的且没有未定义行为。初始化器列表中的表达式从左到右评估并在S的构造函数执行之前排序。因此,您的程序应该始终将值2分配给变量i

C++标准第8.5.4节的引用:

"在大括号初始化列表的初始化器列表中,包括任何由pack expansions(14.5.3)产生的初始化程序子句,按出现顺序进行评估。也就是说,与给定初始化程序子句相关的每个值计算和副作用都排在初始化程序列表中后跟它的任何初始化程序子句相关的每个值计算和副作用之前。"

因此,发生的情况是:

  1. 评估++i,得到i = 1S构造函数的第一个参数);
  2. 评估++i,得到i = 2S构造函数的第二个参数);
  3. 执行S的构造函数;
  4. 执行S的转换运算符,返回值2
  5. 将值2分配给i(它已经有了值2)。

标准的另一个相关段落是第1.9/15节,其中还提到了类似的具有未定义行为的示例:

i = v[i++]; // the behavior is undefined
i = i++ + 1; // the behavior is undefined
然而,同一段落中还提到:

"除非特别说明,单个运算符的 操作数和单个表达式的子表达式的评估是未排序的。[...] 在调用函数(无论函数是否为内联函数)时,与任何参数表达式相关联的每个值计算和副作用,或者称为被调用函数的后缀表达式的副作用,都将在执行所调用函数体中的每个表达式或语句之前排序。"

由于1)初始化列表中表达式的评估从左到右排序,2)S 类的构造函数的执行顺序在初始化列表中所有表达式的评估之后,3)对 i 的赋值在执行 S 的构造函数(及其转换运算符)之后排序,因此行为是良好定义的。


你确定吗?在C++03中,i = ++i;(例如)是未定义的行为,即使评估顺序是有保证的,因为i在序列点之间被修改了两次。我不是C++11专家 - 我知道C++11放弃了序列点的概念 - 但这些示例在我看来非常相似。 - Nemo
1
@Nemo:C++11仍然具有顺序概念,只是为了考虑多个线程而比“序列点”的概念更加复杂。但是,答案缺少引用句子后面更关键的一句话:“也就是说,与给定初始化器子句相关联的每个值计算和副作用都在逗号分隔的初始化器列表中跟随它之后的任何初始化器子句相关联的每个值计算和副作用之前进行排序。”因此,代码确实是定义良好的。 - Mike Seymour
@Nemo 但是我的 int() 重载中不是有一个序列点吗?这不是使行为定义明确了吗? - David G
@Mike:如果 f 是一个普通函数,那么 i = f(++i) 是被定义的吗? - Nemo
1
@Nemo,这在C++11之前也是定义良好的,尽管评估顺序是未指定的。我创建了一个自问自答的问题,涵盖了这个C++11之前的问题,因为这是另一个问题的争议点,而且自己弄清楚这个问题是很有启发性的。 - Shafik Yaghmour
显示剩余6条评论

-3

确实,您遇到了未定义行为的情况。

以下是导致未定义行为的情况的示例:

  • 在一个序列点内多次更改变量。经典的例子是 i=i++ 表达式,其中 i 变量的赋值和增量同时进行。要了解更多关于这种错误的信息,请阅读"序列点"部分。
  • 在初始化变量之前使用变量。尝试使用该变量时会发生未定义行为。
  • 使用 new [] 运算符进行内存分配,然后使用 delete 运算符进行释放。例如:T *p = new T[10]; delete p;。正确的代码是:T *p = new T[10]; delete [] p;。

编辑

另外,您的代码 S{++i, ++i}; 无法在 VS2012 中编译。也许您的意思是 S(++i, ++i);?如果使用“()”,则存在未定义行为。否则,您的源代码是不正确的。


4
这里没有未定义行为:花括号初始化语法是C++11的常规语法,VS2012不支持它是该IDE的限制。当使用花括号初始化时,按照我在答案中引用的标准段落,表达式从左到右进行评估。其次,他没有在初始化之前使用变量。该变量是全局的,并初始化为0。所以如果你是那个对我的回答投了反对票的人,你可能需要重新考虑你的决定 :-) 这个答案对于C++11是正确的。 - Andy Prowl
1
直到VS2015,C++11才得到了完全支持。花括号初始化在VS2013中得到了支持,但在VS2012中不支持。 - Michael Petch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接