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 未初始化相同的质量,但事实并非如此。
另请注意,静态持续时间情况是此处的额外主题...
这里出现了一个有趣的问题作为其他问题的副作用,关于 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, wheren
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 未初始化相同的质量,但事实并非如此。
另请注意,静态持续时间情况是此处的额外主题...