C++ 在聚合初始化期间是否对未指定的值进行零初始化?

Does C++ zero-initialise unspecified values during aggregate initialization?

这里出现了一个有趣的问题作为其他问题的副作用,关于 C 和 C++ 处理方式(非静态存储持续时间)之间的可能差异:

int arr[7] = {0};

有人说,在 C++ 中,其他元素不能保证为零,但我不确定我是否同意。

现在 C11 声明,在 6.7.9 Initialization /19:

The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject; all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.

这意味着 arr 的其他六个元素将 初始化为零(因为 static int x; 会将 x 初始化为零) .


我不确定 C++ 是否也是这种情况。在 C++20 标准中,9.3.1 Aggregates /3 规定:

When an aggregate is initialized by an initializer list as specified in 9.3.4, the elements of the initializer list are taken as initializers for the elements of the aggregate. The explicitly initialized elements of the aggregate are determined as follows:

(3.1) — (irrelevant stuff to do with designated initialiser lists and classes - pax).

(3.2) — If the initializer list is an initializer-list, the explicitly initialized elements of the aggregate are the first n elements of the aggregate, where n is the number of elements in the initializer list.

然后 /4 说明 显式 初始化如何工作,/5 处理非显式情况:

For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:

(5.1) — If the element has a default member initializer (10.3), the element is initialized from that initializer.

(5.2) — Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list (9.3.4).

(5.3) — Otherwise, the program is ill-formed.

在我看来,我们的特殊情况包含在 (5.2) 中,因此我们必须转到 9.3.4 以查看用空列表初始化的 int 会发生什么({}).这经历了很多情况,但我相信第一个匹配的是:

(3.11) — Otherwise, if the initializer list has no elements, the object is value-initialized.

并且,来自 9.3 Initializers /8

To value-initialize an object of type T means:

(8.1) — if T is a (possibly cv-qualified) class type (Clause 10) with either no default constructor (10.3.4) or a default constructor that is user-provided or deleted, then the object is default-initialized;

(8.2) — if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;

(8.3) — if T is an array type, then each element is value-initialized;

(8.4) — otherwise, the object is zero-initialized.

因此,8.4 似乎是控制子句,这意味着 C++ 还将 将数组的非显式元素初始化为零。

我的推理是否正确? C++ 会在遇到 int arr[7] = {0}; 时将所有元素设置为零吗?

是的。 C++ 通常保持与 C 的向后兼容性,允许您包含和使用 C 代码。考虑一下你是否有一些遗留的 C 代码,它会像你描述的那样尝试初始化一个数组:

int arr[7] = {0};

如果 C++ 的工作方式有任何不同,并且 C 程序(在 C 下是有效的)假定此数组是零初始化的,那么如果包含在使用 C++ 编译器编译的 C++ 项目中,代码可能会失败。

为了确认,我在 x64 上使用 Cygwin g++ 编译了这个 C++ 程序 Windows:

int main() {
    int arr[7] = {0};
}

然后在GDB中反汇编函数main

push   %rbp
mov    %rsp,%rbp
sub    [=11=]x40,%rsp
callq  0x1004010d0 <__main>
movq   [=11=]x0,-0x20(%rbp)
movq   [=11=]x0,-0x18(%rbp)
movq   [=11=]x0,-0x10(%rbp)
movl   [=11=]x0,-0x8(%rbp)
mov    [=11=]x0,%eax
add    [=11=]x40,%rsp
pop    %rbp
retq

如您所见,程序将 3 个 qword 和 1 个 dword 的零值移入堆栈。那是 28 个字节,这是我系统上 7 个整数的大小。

为了在此处添加相当官方的参考,一个非常晚的附加答案:

cppreference (for C) with Array initialization 有一个明确的例子:

int a[3] = {0}; // valid C and C++ way to zero-out a block-scope array
int a[3] = {}; // invalid C but valid C++ way to zero-out a block-scope array

所以你的具体例子的事情是完全清楚的。

Someone was stating that, in C++, the other elements were not guaranteed to be zero but I'm not sure I agree.

这是完全错误的。截至同一参考:

All array elements that are not initialized explicitly are zero-initialized

C++ 标准必须明确标记这种缺失的向下兼容性,但它没有。但是从 http://eel.is/c++draft/dcl.init#general-16.5 开始,对于具有聚合抽象的 C++:

The remaining elements are initialized with their default member initializers, if any, and otherwise are value-initialized.

整数为零,参见Zero initialization:

If T is a scalar type, the object's initial value is the integral constant zero explicitly converted to T.

所以int a[3] = {0};实际上并不是编译器说“将所有元素写入零”的单一语句,而是两个步骤的顺序:“用这个值显式初始化第一个元素(恰好是已经为零),根据它们的默认值初始化其余元素 initializers/default 值初始化,即 int 为零。

这里有点混乱的原因可能是 C 适应的 C++ 标准草案使一些事情变得非常抽象。许多程序员可能会认为剩余元素的值初始化可能具有与 variables/members 未初始化相同的质量,但事实并非如此。

另请注意,静态持续时间情况是此处的额外主题...