【问题标题】:Evaluation order in initialization初始化中的评估顺序
【发布时间】:2017-04-03 16:07:58
【问题描述】:

在以下程序中:

#include <iostream>
struct I {
    int i;
    I(){i=2;}
    I(int _i){i=_i;}
};
int a[3] = {a[2] = 1};
int aa[3][3] = {aa[2][2] = 1};
I A[3] = {A[2].i = 1};
I AA[3][3] = {AA[2][2].i = 1};
int main(int argc, char **argv) {
    for (int b : a) std::cout << b << ' ';
    std::cout << '\n';
    for (auto &bb : aa) for (auto &b : bb) std::cout << b << ' ';
    std::cout << '\n';
    for (auto &B : A) std::cout << B.i << ' ';
    std::cout << '\n';
    for (auto &BB : AA) for (auto &B : BB) std::cout << B.i << ' ';
    std::cout << '\n';
    return 0;
}

输出是

1 0 0 
1 0 0 0 0 0 0 0 1 
1 2 2 
1 2 2 2 2 2 2 2 2

来自 http://ideone.com/1ueWdK 和 clang3.7

但结果是:

0 0 1 
1 0 0 0 0 0 0 0 1 
1 2 2 
1 2 2 2 2 2 2 2 2 

http://rextester.com/l/cpp_online_compiler_clang 上也使用 clang 3.7。

在我自己的 ubuntu 上,gcc 6.2 在构造 int aa[3][3] = {aa[2][2] = 1} 上出现内部编译器错误。

我假设这是未定义的行为,但在标准中找不到明确的声明。

问题是:

标准中定义的初始化列表(例如a[2] = 1)中赋值的副作用和数组实际元素的初始化(例如a[2])的评估顺序?

明确声明为已定义或未定义?还是因为没有明确定义而变得未定义?

或者由于评估顺序之外的其他原因,构造是否具有已定义或未定义的行为?

【问题讨论】:

  • 我的眼睛在流血......如果这不是一个赞成票,我不知道会是什么......
  • Doesn't compile。如果您的代码如此混乱以至于编译器的解析器崩溃,也会导致内部编译器错误。
  • 在 gcc 4.9 中编译并且 clang ... gcc 5+ 有一个错误

标签: c++ initialization language-lawyer aggregate-initialization


【解决方案1】:

让我们从最简单的情况开始:

I A[3] = {A[2].i = 1};
I AA[3][3] = {AA[2][2].i = 1};

由于违反了 [basic.life],这两个都是 UB。您正在对象的生命周期开始之前访问它的值。 I 没有普通的默认构造函数,因此不能空初始化。因此,对象的生命周期仅在构造函数完成后才开始。当您访问 A 数组的元素时,尚未构造该数组的元素。

因此,您通过访问尚未构造的对象来调用 UB。

现在,其他两种情况更复杂:

int a[3] = {a[2] = 1};
int aa[3][3] = {aa[2][2] = 1};

请参阅,int 允许“空初始化”,如 [basic.life]/1 所定义。已获取 aaa 的存储空间。因此,int a[3]int 对象的有效数组,即使聚合初始化尚未开始。所以访问对象,甚至设置它的状态都不是UB。

这里的操作顺序是固定的。即使在 C++17 之前,初始化器列表的元素的初始化也是在调用聚合初始化之前排序的,如 [dcl.init.list]/4 中所述。聚合中未在此处初始化列表中列出的元素将被填充,就像由 typename{} 构造一样。 int{} 表示对 int 进行值初始化,结果为 0。

因此,即使您设置了a[2]aa[2][2],它们也应该立即通过聚合初始化被覆盖。

因此,所有这些编译器都是错误的。答案应该是:

1 0 0 
1 0 0 0 0 0 0 0 0

现在承认,这都是非常愚蠢的,你不应该这样做。但从纯语言的角度来看,这是定义明确的行为。

【讨论】:

  • 一个小问题:有一个特殊规定是在对象的生命周期开始之前使用对象here,这导致this clause 说您可以在对象的构造函数启动后立即访问对象成员 - 不是完成。
猜你喜欢
  • 2023-03-03
  • 2010-12-08
  • 1970-01-01
  • 2013-06-30
  • 2010-11-17
  • 2015-08-10
  • 2019-01-05
相关资源
最近更新 更多