cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2821 [basic.life] It is unclear whether the lifetime of an object begins before dynamic initialization #372

Open Eisenwave opened 1 year ago

Eisenwave commented 1 year ago

Full name of submitter: Jan Schultke

Reference (section label): [basic.life]

Issue Description

It is popular opinion in the C++ community that the lifetime of an object begins before dynamic initialization, e.g. https://stackoverflow.com/a/42365867/5740428. This is even considered an idiom, e.g. int x = x; may be used to suppress warnings about a global variable being unused, and is believed to be well-defined. There are examples in the standard which support this belief. See [basic.start.static] Note 2. The following note considers access to d1 well-defined, implying that its lifetime has begun and its value can be used, even before dynamic initialization:

inline double fd() { return 1.0; }
extern double d1;
double d2 = d1;     // unspecified:
                    // either statically initialized to 0.0 or
                    // dynamically initialized to 0.0 if d1 is
                    // dynamically initialized, or 1.0 otherwise
double d1 = fd();   // either initialized statically or dynamically to 1.0

However, not all wording clearly indicates that this is well-defined. In [basic.life] p1.2, it is said that an object's lifetime begins when:

its initialization (if any) is complete (including vacuous initialization)

Footnote 23 for [basic.life] p6 mentions an example of an object which's lifetime hasn't begun, but for which storage was allocated:

For example, before the dynamic initialization of an object with static storage duration

We are not allowed to use such an object, because according to [basic.life] p7.1:

[...] The program has undefined behavior if: (7.1) the glvalue is used to access the object

In summary, footnote 23 suggests that [basic.start.static] Note 2 actually contains undefined behavior because the initialization is not complete, and the lifetime of d1 has not yet begun, which contradicts the code comments in the note.

Furthermore, the singular form of "initialization" in [basic.life] p1.2 could be interpreted in two ways:

  1. the lifetime begins after the whole "process of initialization", where zero-initialization and dynamic initialization combined begin the lifetime
  2. the lifetime begins once after zero-initialization, and then another object transparently replaces the first during dynamic initialization

The first interpretation makes the aforementioned note undefined behavior, the second makes it well-formed.

Suggested resolution

Remove or reword whichever note contradicts WG21 intent. Assuming the intent is that lifetime begins before dynamic initialization, remove Footnote 23, and update [basic.life] p1.2:

-its initialization (if any) is complete (including vacuous initialization)
+its initialization (if any) is complete (including vacuous initialization,
+and zero-initialization for objects which are dynamically initialized)
frederick-vs-ja commented 1 year ago

See also https://github.com/cplusplus/CWG/issues/40#issuecomment-1139501123.

Related: cplusplus/draft#4802.

Perhaps we want zero-initialization starts the lifetime of an object of a trivial type, but not a std::string.

It may also be desired that the lifetime of every subobject starts when its type is trivial (the criteria may be slightly different) and in an active union member.

jensmaurer commented 10 months ago

CWG2821

frederick-vs-ja commented 6 months ago

This program raises SIGSEGV with libstdc++ (Godbolt link):

#include <string>
#include <thread>
#include <iostream>

void fun()
{
    thread_local std::string s = "str"; // On libstdc++, s can't have static initialization.
    std::thread([]{
        // N.B. the constructor hasn't been called for s of this thread.
        std::cout << static_cast<int>(s[s.size()]) << '\n';
    }).join();
}

int main()
{
    fun();
}

It doesn't seem possible to guarantee well-defined behavior for a zero-initialized but not yet dynamically initialized std::string object. Should we treat its lifetime being started?

t3nsor commented 5 months ago

With the current proposed resolution, I think we can strike the part about union members. Given that objects will not begin lifetime until "any initialization is complete (including vacuous initialization)", therefore, the fact that the non-initialized members of the union do not begin their lifetime falls out naturally out of the second bullet, because they do not have any initialization (yet).

t3nsor commented 5 months ago

It doesn't seem possible to guarantee well-defined behavior for a zero-initialized but not yet dynamically initialized std::string object. Should we treat its lifetime being started?

You could ask the same question about a user-written class type where every non-constructor function has, as a precondition, that a constructor has completed.

The fact that std::string is such a standard library type is a specification issue for LWG, not CWG.