21

我对初始化列表和序列点很好奇。不久前我读到初始化列表中的评估顺序是从左到右的。如果是这样,那么评估点之间肯定有某种序列点,我错了吗?那么说的是以下有效代码吗?有什么会导致未定义的行为吗?

int i = 0;

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

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

任何和所有的回应都表示赞赏。

4

2 回答 2

19

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

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

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

因此,发生的事情是:

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

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

i = v[i++]; // the behavior is undefined
i = i++ + 1; // the behavior is undefined

但是,同一段说:

"除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估是无序的。[...] 调用函数时(无论该函数是否内联),每个值计算和与任何参数表达式相关的副作用,或带有指定被调用函数的后缀表达式,在被调用函数主体中的每个表达式或语句执行之前排序。

因为 1) 初始化列表中表达式的求值是从左到右排序的,2) 构造函数的执行S是在初始化列表中所有表达式的求值之后排序的,以及 3 i)的构造函数S(及其转换运算符)的执行,行为是明确定义的。

于 2013-01-21T16:30:00.517 回答
-2

是的,确实你有未定义行为的情况。

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

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

已编辑

还有你的代码 S{++i, ++i}; 不是为 VS2012 编译的。可能你的意思是 S(++i, ++i);?如果您使用“()”,则存在未定义的行为。在其他情况下,您的源代码不正确。

于 2013-01-22T12:49:06.887 回答