Does C++ default-initialization preserve prior zero-initialization?
Asked Answered
E

1

10

If a C++ constructor for an object with static-storage duration does not initialize a member, is that required to preserve the prior zero-initialization, or does it leave the member with an indeterminate value?

My reading of the C++ spec is that it contradicts itself.

Example:

#include <iostream>

struct Foo { Foo(); int x; } object;

Foo::Foo() { }

int main() { std::cout << object.x << std::endl; }

The Foo() constructor does not explicitly initialize the member object.x, so according to the note in 12.6.2 paragraph 8:

the member has indeterminate value.

But working through the details of the various initializations, this appears to be incorrect. The member object.x is zero-initialized as it has static-storage-duration, and then I can't see anything that changes that.

Regarding the constructor, the text in 12.6.2 that applies is:

the entity is default-initialized.

In 8.5 paragraph 7, the relevant case of default initialization is:

... no initialization is performed

which I read to mean that the previous zero-initialization is not changed by the default-initialization.

Am I missing some other text which resets all members to "indeterminate value" at the start of the constructor call?

I found various other questions on stackoverflow regarding zero-initialization and default-initialization, but I couldn't see any that analyzed what happens when default-initialization follows some early initialization of the same entity.

In this case there is probably no practical effect. But in a more complex constructor, with some members initialized and others not, does the compiler have to track exactly which bytes/bits are initialized?, or can it just initialize the whole object (e.g., simplifying the constructor to a memset() call)?

Elongate answered 31/10, 2015 at 20:17 Comment(16)
I removed my answer because there's a twist that I'm not sure about: Foo is an object with non-trivial initialization, which means its lifetime only has started once its constructor completed. I am not sure about the values of its members, then. Before the constructor has started execution, you may not even access them (by normative rules), so I have a hard time that they have value values (zero) stored by that time.. not to mention the existence of an object in the first place.Baloney
@LightnessRacesinOrbit re "Eh no if it's only in draft then it is not in a standard and should not be relied on".. then we have a hard time writing most real-world code, I think. Given how many defect reports (some still open) attempt to fix bugs in the standard :) For example, open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#191Baloney
@ᐅJohannesSchaub-litbᐊ actually, I think your deleted answer was a good one. I have a hard time making any sense of zero initialization if the static-storage-duration object completely does not exist prior to the constructor call. E.g., would it be valid for a compiler to in-the-background replace "object" with a hidden pointer which gets initialized by "new Foo()"?Elongate
@Johannes: I agree; what a mess!Raman
@LightnessRacesinOrbit - the reply you commented on is deleted, but I think in this case, the "fix" is a "clarification" not a "correction", as the wording that appears questionable is in a "note", which I assume is non-normative.Elongate
the note in 3.8p2 says " 12.6.2 describes the lifetime of base and member subobjects.".. but 12.6.2 nowhere mentions the word "lifetime", nor can I infer what their lifetime might be. So if we go by the general rule in 3.8p1 and apply it also to member subobjects, the member subobject exists as soon as storage for it exists, and I would agree that the member has value zero and you can access it. But I'm totally unsure.Baloney
@ᐅJohannesSchaub-litbᐊ There is text somewhere saying that the storage-duration of a member is the same as the storage-duration of the object containing it. So in this case "object.x" is an "int" with static storage-duration ... the only way I can reconcile that with "object" having a more restricted lifetime, is that "object.x" exists independently of the lifetime of "object" (even if using it is undefined). But maybe something in the 1000+ page mess of the ISO spec says differently....Elongate
@Elongate i'm tending more towards that this is alright. 12.6.2p6 also says that the value is indeterminate if the member wasn't initialized. but it doesn't say wasn't initialized by the constructor. Zero initialization counts aswell, so these don't conflict, IMO.Baloney
@ᐅJohannesSchaub-litbᐊ I think you are probably right on a completely literal reading of the wording. But in the context of discussing the initializations carried out by the constructor, it is at best deeply confusing to implicitly refer to all initializations... Also, what if the member has been assigned a value (as opposed to initialized), prior to the constructor call? E.g., if an heap object has an explicit destructor call followed by a placement new, along the lines of #11638111Elongate
@Elongate in that example, the reference refers to an object that was destructed (and a new object was created there). The reference will not automatically be re-seated to the new object (rules at 3.8p7). So the rules here are at 12.7p3: "To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior. ". So once you try to read from the reference, it's UBBaloney
Note that the rule does not say that the lifetime of the members ended (which I don't think would harm, actually I think that would be a good thing if that would be made explicit there), but it still behaves as if the lifetime has ended with regard to whether accessing the value is valid or not.Baloney
@ᐅJohannesSchaub-litbᐊ Ok, how about this example: you heap allocate memory for a POD-type (::operator new(sizeof(Pod))), assign a member (via a cast), and then explicitly call a (non-default, non-copy) constructor which does nothing? Then the member has never been initialized. Does the member still contain the assigned value?Elongate
@Elongate i think the assignment to the member using the cast gives rise to both the member object and the surrounding struct object (this is not explicit in the spec and subject of current bugfixing. See open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1116 ). The placemenet-new always creates a new object. And you then need to access the member again, to get a reference to the new object. Using your old pointer then refers to an out-of-lifetime member because you reused its storage for a new object, I think, and reading its object falls afoul of 3.8p5.Baloney
This is valid: S *p = malloc(sizeof*p); p->a = 1; new (p) S; /* p now refers to a different, new, object */ int x = p->a; but this is not valid: S *p = malloc(sizeof*p); int *pa = &p->a; *p = 1; new (p) S; /* p now refers to a different, but pa is dead and not reseated */ int x = *pa; /* UB */. But remember: I am not an expert on this, and all I say could be all-wrong! This isn't super-clear in the spec...Baloney
@ᐅJohannesSchaub-litbᐊ Ok, I am convinced. For a sufficiently precise reading of the word "initialized", the note in 12.6.2p8 is correct, and in my original example does not say "object.x" gets an indeterminate value. But it is confusing, and I am not sure that particular reading of the word "initialized" is either unique or intended.Elongate
Note there is a slight twist in the case where we have constant initailization although that does not effect this case.Uzziel
U
4

Defect report 1787 lead to the change documented in N3914 being applied to the draft standard for C++14. Which change [dcl.init] paragraph 12 from:

If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value. [ Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2. — end note ]

to:

If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (5.17 [expr.ass]). [Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2 [basic.start.init]. —end note] If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:

[...]

This make it clear the indeterminate value situation only occurs for objects of automatic or dynamic storage duration. Since this was applied via a defect report it probably also applies to C++11 since the defect report occured before C++14 was accepted but it could apply further back as well. The rules for how far back a defect are supposed to apply were never clear to me.

Since placement new was brought up in the comments, the same change also modified section [expr.new], making the indeterminate value portion a comment:

If the new-initializer is omitted, the object is default-initialized (8.5 [dcl.init]); if. [Note: If no initialization is performed, the object has an indeterminate value. —end note]

The beginning of the section says:

[...]Entities created by a new-expression have dynamic storage duration (3.7.4).[...]

Which seem sufficient to apply the changes in section [dcl.init].

This change was also interesting since prior to this change the term indeterminate value was not defined in the C++ standard.

Uzziel answered 1/11, 2015 at 0:8 Comment(1)
The new wording is extremely clear in this case. It is not quite so clear for a placement-new (the rules for which storage-duration apply?). My reading there is that any prior initialization is irrelevant & the behaviour is as per automatic/dynamic storage duration. E.g., if you have a static-duration char array, and then call placement-new to construct an object in the array, then the zero-initialization of the char array is irrelevant?Elongate

© 2022 - 2024 — McMap. All rights reserved.