1
votes

An interesting question arose as a side effect of some other question here, about the possible differences between how C and C++ handle (the non-static-storage-duration):

int arr[7] = {0};

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

Now C11 states, in 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.

This means that the other six elements of arr will be initilised to zero (since static int x; would initialise x to zero).


I'm unsure if this is also the case for C++. In the C++20 standard, 9.3.1 Aggregates /3 states:

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.

Then /4 states how the explicit initialisations work, and /5 handles the non-explicit cases:

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.

It appears to me that our particular case is covered by (5.2) so we have to go to 9.3.4 to see what happens to an int initialised with an empty list ({}). This goes through a lot of cases but I believe the first one that matches is:

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

And, from 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.

Hence, it's 8.4 which seems to be the controlling clause, meaning C++ will also initialise the non-explicit elements of the array to zero.

Is my reasoning correct? Will C++, on encountering int arr[7] = {0};, set all elements to zero?

2
"Someone was stating that, in C++, the other elements were not guaranteed to be zero" Where was this said?Nicol Bolas
int arr[7] = {0}; will explicitly initialize the first element to zero, then the compiler will add code to initialize the remaining elements to zero.Some programmer dude
Short answer: YesArdent Coder
@NicolBolas: on rereading the comment, they actually stated that int arr[n]={value} didn't guarantee that all elements would be value. So, they're correct for value != 0 but, since I was using {0} as the init list, I thought it strange. I just wanted clarification since ISO C++ is so much more headache-inducing than ISO C :-)paxdiablo
My understanding is "yes" - your reasoning and tracking through current (draft?) standard seems appropriate. Although recent standards have made things more complicated, the answer was also unambiguously "yes" in the 1998 C++ standard - and I would be very surprised if a subsequent standard would quietly change that, since a lot of existing code would break.Peter

2 Answers

3
votes

Yes. C++ generally maintains backward compatibility with C, allowing you to include and use C code. Consider if you have some legacy C code, that attempts to initialize an array like you describe:

int arr[7] = {0};

If C++ worked any differently, and the C program (validly, under C) assumed that this array is zero initialized, then the code is liable to fail if included in a C++ project compiled with a C++ compiler.

Just to confirm, I compiled this C++ program using Cygwin g++ on x64 Windows:

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

and then disassembled function main in GDB:

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

As you can see, the program moves 3 qwords and 1 dword worth of zeros into the stack. That's 28 bytes, which is the size of 7 ints on my system.

1
votes

An extremely late additional answer in order to add a quite official reference here:

cppreference (for C) with Array initialization has a clear example for this:

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

So things for your concrete example are totally clear.

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

That's simply wrong. As of the same reference:

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

The C++ standard would have to mark this missing downward-compatibility explicitly but it doesn't. But as of http://eel.is/c++draft/dcl.init#general-16.5 for C++ with abstraction to aggregates:

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

That is zero for integers, see Zero initialization:

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

So int a[3] = {0}; isn't actually a single statement for the compiler to say "write all elements to zero", but a two steps order: "Initialize the first element explicitly with this value (happen to be zero already), initialize the remaining elements according their default value initializers/default value initialization, that is zero for int".

The reason why there's a bit confusion here might be the fact, that the C++ standard drafts for the C-adaption made several things quite abstract. So many programmers might think of value initialization for the remaining elements could be of the same quality like leaving variables/members non-initialized, but that's not true.

Also be aware, that the static duration case is an extra-topic here...