初始化程序列表中的多个突变是未定义的行为?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (22)

我很好奇初始化列表和序列点。我前一段时间读到,初始化列表中的评估顺序保持不变。如果是这样,那么评估点之间肯定有某种顺序点,我错了吗?所以说这是下面的有效代码?有没有什么会导致未定义的行为?

int i = 0;

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

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

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

提问于
用户回答回答于

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

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

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

因此,会发生什么情况是:

  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(及其转换运算符)的构造函数的执行,行为是明确的。

用户回答回答于

是的,你确实遇到了未定义的行为。

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

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

扫码关注云+社区