cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2914 [basic.start.static] Unclear order of initialization of static and thread local variables #559

Open t3nsor opened 3 months ago

t3nsor commented 3 months ago

Full name of submitter: Brian Bi

Issue description: It is heavily implied, but not stated outright, that initialization of all non-local static variables happens before initialization of all non-local thread-local variables. For example, [basic.start.static] seems to refer to initializing variables with static storage duration and thread storage duration as "phases", which may imply that one phase completes before the other starts. [basic.start.term]/2 appears to guarantee that returning from main destroys all thread-local variables of the main thread before destroying any static variables, which would be peculiar if the static variables were not initialized first.

Suggested resolution: Edit [basic.start.static]/1 as shown:

Variables with static storage duration are initialized as a consequence of program initiation. Variables with thread storage duration are initialized as a consequence of thread execution. The initialization of all non-block variables with static storage duration (if any) strongly happens before the initialization of any non-block variable with thread storage duration. Within each of these phases of initiation, initialization occurs as follows.

randomnetcat commented 3 months ago

The suggested resolution seems too strong. In the case of deferred dynamic initialization, the dynamic initialization should not happen before the initialization of (at least some) thread storage duration variables.

Consider the following example, where whatever() is something that prevents constant-initialization:

// TU A
int a = whatever();
thread_local int b = whatever();

void foo();

int main() {
    foo();
}

// TU B
static int c = whatever();

void foo() { return c; }

My understanding is that initializing a, then b, then c is intended to be permitted in the status quo.

t3nsor commented 3 months ago

OK, how about this.

Edit [basic.start.static]/1:

Variables with static storage duration are initialized as a consequence of program initiation. Variables with thread storage duration are initialized as a consequence of thread execution. All non-deferred ([basic.start.dynamic]) initialization (if any) of non-block variables with static storage duration strongly happens before any non-deferred initialization of non-block variables with thread storage duration. Within each of these phases of initiation, initialization occurs as follows.

Edit [basic.start.dynamic]/5:

It is implementation-defined whether the dynamic initialization of a non-block non-inline variable o with static storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as ~the variable to be initialized~ o and any deferred initialization of non-inline non-block variables with thread storage duration defined in the same translation unit as o.39 It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.

randomnetcat commented 3 months ago

Interestingly, static initialization is arguably "non-deferred", so the first paragraph would make static initialization of variables with static storage duration happen before static initialization of variables with thread storage duration. I don't think that's a problem, though?

The second paragraph doesn't appear to ensure that deferred dynamic initialization of variables with thread storage duration happens before an odr-use of a function or variable in the same TU, but I think that's also a problem with the status quo.

Otherwise, I think this looks fine?

t3nsor commented 3 months ago

so the first paragraph would make static initialization of variables with static storage duration happen before static initialization of variables with thread storage duration. I don't think that's a problem, though?

That might be what the status quo is trying to say anyway with its "within each of these phases" wording.

The second paragraph doesn't appear to ensure that deferred dynamic initialization of variables with thread storage duration happens before an odr-use of a function or variable in the same TU

Deferred dynamic initialization of thread local variables is governed by p7. In p5, it's true that adding the suggested guarantee would possibly allow a function in the same TU to observe a thread local variable prior to its dynamic initialization. However, such a use of that function would not be a "non-initialization odr-use" because it would be caused by the initialization of a static variable, so it wouldn't violate p7.

randomnetcat commented 3 months ago

Ah, yes, sorry. I missed that paragraph. Looks fine then.

jensmaurer commented 2 months ago

CWG2914