C++ Logo

std-discussion

Advanced search

Re: Destruction order of statically-initialized objects (like std::mutex)

From: Thiago Macieira <thiago_at_[hidden]>
Date: Thu, 24 Oct 2019 13:02:11 -0700
On Thursday, 24 October 2019 01:02:34 PDT Kevin Bracey via Std-Discussion
wrote:
> Citation here? As far as I can see there's no "regular initialisation
> order problem" - having the constexpr constructor means it must be
> initialised first. I think we only have destruction order problems. I
> don't see any "literal type" constraints.

The passage you quoted below is sufficient. The *initialisation* of the object
is done at static time, but it must still register a non-constexpr destructor
for running and that can only be done at runtime. So your object still incurs
load-time performance penalties.

On some platforms. Upgrade to Linux to avoid this problem, where a
pthread_mutex has no acquired resources.

> "Dynamic initialisation of atexit destruction" isn't standardese - I can
> see what the practical problem is, but I'm trying to pin down the
> specific wording in terms of destruction execution order.
>
> There are potential other odd results - if MyDynamic's constructor can
> use std::mutex, it could also choose to exit after doing so, and it
> would be very strange if that exit didn't run std::mutex's destructor
> because it hasn't been registered yet - can you really skip the
> destruction of an object you've already legally used?

Apparently, yes.

> >> // Output:
> >> // Construct MyDynamic
> >> // Destroy MyMutex
> >> // Destroy MyDynamic
> >
> > Seems right to me. MyDynamic is constructed first, then MyMutex;
> > destruction is reverse, so MyMutex first then MyDynamic.
>
> I believe MyMutex is constructed first, because it's
> constant-initialized, unless there's an exception to
> [basic.start.static] I've missed; so destruction is not reverse.

The initialisation of the destructor is done after MyDynamic, even if the
actual construction of the object was before.

> >> [basic.start.term] says
> >> "If the completion of the constructor or dynamic initialization of an
> >> object with static storage duration strongly happens before that of
> >> another, the completion of the destructor of the second is sequenced
> >> before the initiation of the destructor of the first. [...] If an object
> >> is initialized statically, the object is destroyed in the same order as
> >> if the object was dynamically initialized."
> >>
> >> What does that "as if" mean? "Obey the ordering rules for
> >> dynamically-initialised objects, bearing in mind that static objects are
> >> initialised first" or "Schedule destruction based on the order this
> >> would have been dynamically initialised"?
> >
> > The latter, which is the behaviour your code shows.
>
> If you're believing MyMutex is constructed second, as you said above,
> then my example code and its output doesn't relate to the question -
> obviously Dynamic, Mutex, ~Mutex, Dynamic would be correct - no-one is
> ever going to object to reverse-order destruction. I'm asking if the
> non-reverse-order Mutex, Dynamic, ~Mutex, ~Dynamic is correct.

It's not because it's not what's happening. The problem is that Mutex has two
steps in construction.

Mutex::Mutex(), Dynamic, ~Mutex() registration, ~Mutex(), ~Dynamic

> Cppreference doesn't seem to say anything about destructors for
> statically-initialized objects, except this interpretation, which I was
> considering the "as if" clause might be intending: "if the compiler
> opted to lift dynamic initialization of an object to the static
> initialization phase of non-local initialization, the sequencing of
> destruction honors its would-be dynamic initialization. "

Indeed.

> But that's a different case to an object where early static
> initialization is required.

Right too.

> >> Shouldn't all statically-initialised objects get first dibs on atexit
> >> registration? Otherwise std::mutex's constexpr constructor only does
> >> half the ordering job.
> >
> > No, because it's impossible to do that. You'd still interleave the
> > registration of the destructors after the constructors of another TU.
>
> It's possible if you have a priority-based init arrays, which exist in
> some systems. Each TU can put an entry into "_init_array.0" which
> registers any destructors for statically-initialized objects, and/or an
> entry into the normal "_init_array" which runs the constructors and
> registers destructors for dynamically-initialized objects. The linker
> can sort those across TUs into a single array.

That only pushes the problem further: what happens to dynamic libraries? You'd
need a full breadth scanning of statically constructed objects' destructor
registration, then dynamic construction & destructor registration.

And then there's dynamic loading.

> GCC already exposes manual priority-based init via
> __attribute__(constructor(priority)), so the linker/system support
> probably exists quite generally.

No, it does not. MSVC has no functionality. And of course, the GCC one is
available for the user, so the user can set a higher priority and bypass the
compiler setting.

We ran into a similar problem with Qt, where certain objects that we expected
to be statically initialised (because they were POD and initialised by
constant expressions) weren't on Windows. For MinGW, we worked around by
setting an arbitrarily high priority, but the same solution wasn't available
for MSVC. The solution for Qt 6 was a rearchitecting that added an extra level
of indirection.

-- 
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel System Software Products

Received on 2019-10-24 15:04:31