[RFC] Suppress C++ static destructor registration

Hi folks,

I’d like to add a flag in clang, -fno-cxx-static-destructors, which allows developers to demand that no static destructors be emitted. Bruno has a sample implementation. We’ve discussed this previously but I’d like to re-open the discussion and make a different case for it because we’ve received more requests for such a feature.

Why is this desirable?

In low-memory circumstances it’s often the case that we know that static global destructors are never called. It would be useful to avoid emitting them entirely to save memory. We can’t necessarily make the types themselves trivially destructible (e.g. a std::map, or a type that’s used both as a global and as an automatic variable for which the destructor is only meaningful when automatic, or coming from 3rd party library such as boost), and using a NeverDestroyed class or global pointer only (std::string& foo = *new std::string(“derp"):wink: prevents constexpr and is annoying boilerplate (and again, 3rd party code breaks that party).

This is also useful for some thread-related use cases: we have empirical evidence that threads using globals cause crashes if these globals are being destroyed.

Thread-local storage is similarly painful for different reasons. I’m not proposing that anything be done yet, but let’s keep it in mind.

Developers want this?

Yes, we’ve received numerous requests for this. Developers are worried about code footprint, and have numerous crash reports they’d like to get rid of. Developers tell us they’d really rather not pay for this feature, because they don’t want to use it yet are stuck with it (and C++ is a “don’t pay for what you don’t use” language).
Interesting note: developers are used to having no cleanup on termination on platforms where applications can get terminated when e.g. they’re sent to the background by user action.

Concrete example

Greg Parker provided this example of thread-related issues in the previous discussion:

The Objective-C runtime has a global table that stores retain counts. Pretty much all Objective-C code in the process uses this table. With global destructors in place this table is destroyed during exit(). If any other thread is still running Objective-C code then it will crash.

Currently the Objective-C runtime avoids the destructor by initializing this table using placement new into an aligned static char buffer.

I’m assuming that the embedded usecase is obvious enough to everyone to not need an example. Let me know if that’s not the case.

What about standardization?

If this works out I’ll discuss standardization options through SG14 (and then WG21). The type of developer who’s asked for this are typical SG14 targets (embedding, gaming, etc). This might fit in with “freestanding” or other similar SG14 efforts, but we need experience to guide the proposal. Maybe EWG will be interested as well? :man_shrugging:

Thanks,

JF

+1 on adding this functionality.

HOWEVER, I think the example of an object (e.g. the objc runtime table) which you’d like to not destroy is a rather poor motivating example for the addition of a compiler flag, and would suggest that you rewrite your motivation without that example in it.

The reason to add a compiler-flag is to enable compiling all your code in that way – e.g. if you know you want to never emit global destructors in your application.

For the objc-runtime table, using a “template class NoDestructor” template which does a new-into-std::aligned_storage dance seems like an entirely suitable answer. Or, if that’s not good-enough for some reason, then it’d seem to call for the addition of an attribute to suppress the destructor on the one global it’s attached to.

+1

FWIW, I'm in favour of an attribute alongside/instead of this to mark
which objects should not have their destructors emitted/called (can
apply to function-local static and thread_local objects in C++). I've
had to manually do this a number of times and would appreciate this
extension.

Cheers

Hi folks,

I’d like to add a flag in clang, -fno-cxx-static-destructors, which allows developers to demand that no static destructors be emitted. Bruno has a sample implementation. We’ve discussed this previously but I’d like to re-open the discussion and make a different case for it because we’ve received more requests for such a feature.

(Nit: we generally prefer to use “c++” rather than “cxx” in flag names.)

Why is this desirable?

In low-memory circumstances it’s often the case that we know that static global destructors are never called. It would be useful to avoid emitting them entirely to save memory. We can’t necessarily make the types themselves trivially destructible (e.g. a std::map, or a type that’s used both as a global and as an automatic variable for which the destructor is only meaningful when automatic, or coming from 3rd party library such as boost), and using a NeverDestroyed class or global pointer only (std::string& foo = *new std::string(“derp"):wink: prevents constexpr and is annoying boilerplate (and again, 3rd party code breaks that party).

Conversely, code (I’m also thinking particularly of third-party code) not written to deal with no cleanup on termination may fail to flush buffers or save changes to its configuration or the like, resulting in data loss. So the user needs to do some work at some level – either to verify it’s safe to turn this option on, or to change the code to avoid non-trivial destruction – and that work scales with the quantity of pre-existing non-trivial destructors. Nonetheless, this flag may still be the most pragmatic way to address the problems in question after determining that it’s safe to use it.

This is also useful for some thread-related use cases: we have empirical evidence that threads using globals cause crashes if these globals are being destroyed.

Thread-local storage is similarly painful for different reasons. I’m not proposing that anything be done yet, but let’s keep it in mind.

Developers want this?

Yes, we’ve received numerous requests for this. Developers are worried about code footprint, and have numerous crash reports they’d like to get rid of. Developers tell us they’d really rather not pay for this feature, because they don’t want to use it yet are stuck with it (and C++ is a “don’t pay for what you don’t use” language).
Interesting note: developers are used to having no cleanup on termination on platforms where applications can get terminated when e.g. they’re sent to the background by user action.

Concrete example

Greg Parker provided this example of thread-related issues in the previous discussion:

The Objective-C runtime has a global table that stores retain counts. Pretty much all Objective-C code in the process uses this table. With global destructors in place this table is destroyed during exit(). If any other thread is still running Objective-C code then it will crash.

Currently the Objective-C runtime avoids the destructor by initializing this table using placement new into an aligned static char buffer.

I’m assuming that the embedded usecase is obvious enough to everyone to not need an example. Let me know if that’s not the case.

I find the “I’m not going to exit(), so don’t waste storage on destructors I will never use” case much more compelling than the “my destructor is wrong so please don’t run it” case.

What about standardization?

If this works out I’ll discuss standardization options through SG14 (and then WG21). The type of developer who’s asked for this are typical SG14 targets (embedding, gaming, etc). This might fit in with “freestanding” or other similar SG14 efforts, but we need experience to guide the proposal. Maybe EWG will be interested as well? :man_shrugging:

Somewhat echoing my comments on the previous thread… if we’re implementing something that is also being proposed for standardization (even if that’s going to be via a TS or other side-standard), I think you have a much stronger case for this change than if we’re adding yet another non-standard C++ dialect (or, more accurately, doubling the number of such dialects). See the guidelines for language extensions at http://clang.llvm.org/get_involved.html – I have no doubt that you can address all points here other than 3 and 4, and if you have a paper describing a specification for this feature heading through WG21 with “a reasonable chance of acceptance”, that seems to satisfy those two remaining points.

On the standardization front, I think there is a path to changing ISO C++ that has at least some chance of success:

  1. Add an annotation mechanism to say “this variable intentionally has a non-trivial destructor that must be run at shutdown” and another to say “this variable should not have its destructor run at shutdown even though it’s non-trivial”

  2. Deprecate variables with a non-trivial destructor without one of those annotations

  3. (After some time has passed) switch the default from running destructors to not running destructors

Perhaps we should consider such annotations alongside the flag?

I have hit this issue a few times with similar solutions (either placement new, or simply new and leak the pointer), so I agree that this is a problem that needs solving. That said, I rarely want to disable *all* static destructors, instead I want to either disable some or define ordering between them (which I can do by disabling some and explicitly calling them from another).

I would much prefer to see a [[no_destroy]] attribute or similar, to disable registering destructors for a specific static / global, than a different language mode. I would also see a simplification of some code with a [[lazy_construct]] attribute so that I could declare a global and have it follow the same initialisation rules as a static, rather than having to create a static and a function that returns a reference to it.

I would; however, point out that exit() is not the only time that static destructors are run. The C++ standard pretends that shared libraries don’t exist (and added thread-local variables with nontrivial destructors in such a way that gives implementers two possible bad choices if they have to support library unloading), but in the real world they do. It’s difficult to see how such a mode would coexist with a world that supports loading and unloading of shared libraries. Perhaps a sanitiser could check that the objects have been destroyed when the library is unloaded?

David

My interpretation of the C++ standard is that today's freestanding C++ implementations (between C++98 and C++17) are already allowed to skip global destructors. [basic.start]

So re-label the toolchain from hosted to freestanding, change nothing else except start-up and termination, and you still have a compliant (but unorthodox) implementation.

I've got a paper that discusses some of this (along with a lot of other embedded-hostile features) here: P1105R0: Leaving no room for a lower-level language: A C++ Subset

+1 for a [[no_destroy]] attribute. There are a couple of places libc++ could benefit from having it, but we couldn’t turn of static destructors in general.

Replying to everyone so far. Thanks for the input!

Based on feedback reveiced I think it makes sense to have an attribute, and I’m happy to implement it. I also think a flag makes sense based on developer feedback, and based on Ben’s reading of the standard for freestanding. I don’t think we want to change the freestanding behavior (at least not before C++20 cleans it up), but having a flag to control this behavior makes sense.

I therefore think we should pursue both.

On attributes: I like this attribute for globals, function statics, and TLS. I don’t like it at all for automatic variables, because RAII is fundamental to C++. I think we should limit where the attribute can be used.

Attribute name bikeshed: anyone care for one of

  • [[no_destroy]]
  • [[indestructible]]
  • [[forever]]
  • [[undying]]
  • [[RAINI]] (Resource Acquisition Is Not Initialization)

?

Compiler flag name bikeshed: I’m happy with -fno-c+±static-destructors as Richard suggests.

Individual replies:

Richard said:

Conversely, code (I’m also thinking particularly of third-party code) not written to deal with no cleanup on termination may fail to flush buffers or save changes to its configuration or the like, resulting in data loss. So the user needs to do some work at some level – either to verify it’s safe to turn this option on, or to change the code to avoid non-trivial destruction – and that work scales with the quantity of pre-existing non-trivial destructors. Nonetheless, this flag may still be the most pragmatic way to address the problems in question after determining that it’s safe to use it.

Agreed. Luckily some platforms already terminate program abruptly. These applications already deal with those issues :slight_smile:

On the standardization front, I think there is a path to changing ISO C++ that has at least some chance of success:

  1. Add an annotation mechanism to say “this variable intentionally has a non-trivial destructor that must be run at shutdown” and another to say “this variable should not have its destructor run at shutdown even though it’s non-trivial”
  2. Deprecate variables with a non-trivial destructor without one of those annotations
  3. (After some time has passed) switch the default from running destructors to not running destructors

This path is interesting. I’ll write a paper for the next mailing, or during the meeting, based on clang implementation experience and any usage I can get between now and then. I’ll make sure to mention your suggestion.

David said:

I would; however, point out that exit() is not the only time that static destructors are run. The C++ standard pretends that shared libraries don’t exist (and added thread-local variables with nontrivial destructors in such a way that gives implementers two possible bad choices if they have to support library unloading), but in the real world they do. It’s difficult to see how such a mode would coexist with a world that supports loading and unloading of shared libraries. Perhaps a sanitiser could check that the objects have been destroyed when the library is unloaded?

I’d like to keep shared libraries out of scope of this discussion.

Replying to everyone so far. Thanks for the input!

Based on feedback reveiced I think it makes sense to have an attribute, and
I’m happy to implement it. I also think a flag makes sense based on
developer feedback, and based on Ben’s reading of the standard for
freestanding. I don’t think we want to change the freestanding behavior (at
least not before C++20 cleans it up), but having a flag to control this
behavior makes sense.

I therefore think we should pursue both.

On attributes: I like this attribute for globals, function statics, and TLS.
I don’t like it at all for automatic variables, because RAII is fundamental
to C++. I think we should limit where the attribute can be used.

Attribute name bikeshed: anyone care for one of

[[no_destroy]]
[[indestructible]]
[[forever]]
[[undying]]
[[RAINI]] (Resource Acquisition Is Not Initialization)

?

Out of that list, I think [[no_destroy]] resonates with me the most,
but I don't have a strong preference. Other alternatives:

[[destructor_not_run]]
[[not_destroyed]]
[[no_cleanup]]

One question I have about any of these names is: can we check some
very large bodies of code to ensure there are no use macros with the
same spelling as whatever we seriously consider?

Compiler flag name bikeshed: I’m happy with -fno-c++-static-destructors as

I'd be happy with that flag name.

~Aaron

Replying to everyone so far. Thanks for the input!

Based on feedback reveiced I think it makes sense to have an attribute, and
I’m happy to implement it. I also think a flag makes sense based on
developer feedback, and based on Ben’s reading of the standard for
freestanding. I don’t think we want to change the freestanding behavior (at
least not before C++20 cleans it up), but having a flag to control this
behavior makes sense.

I therefore think we should pursue both.

On attributes: I like this attribute for globals, function statics, and TLS.
I don’t like it at all for automatic variables, because RAII is fundamental
to C++. I think we should limit where the attribute can be used.

Attribute name bikeshed: anyone care for one of

[[no_destroy]]
[[indestructible]]
[[forever]]
[[undying]]
[[RAINI]] (Resource Acquisition Is Not Initialization)

?

Out of that list, I think [[no_destroy]] resonates with me the most,
but I don’t have a strong preference. Other alternatives:

[[destructor_not_run]]
[[not_destroyed]]
[[no_cleanup]]

One question I have about any of these names is: can we check some
very large bodies of code to ensure there are no use macros with the
same spelling as whatever we seriously consider?

Another thing we should consider: if we add both an attribute and a flag, some users will reasonably want an attribute to undo the effect of the flag. So we should pick a name for the attribute that supports a negative form (either attr vs attr(false) or attr vs no_attr or something like that).

It’d also seem preferable to give the attribute and the flag names that parallel one another, if we can do so without sacrificing name quality. (Strawman: [[clang::no_destroy]] and -fno-destroy-statics)

Compiler flag name bikeshed: I’m happy with -fno-c+±static-destructors as

I’d be happy with that flag name.

Do we anticipate ever registering destructors for global ARC pointers (or structs containing same)? If so, we may come to regret giving the flag a C+±specific spelling.

Hi,

On this topic, I work on an embedded system where I have these concerns about static destructor, but I can’t either fully / blindly disable them. Disabling them with a global flag seems dangerous: how can I make sure that no-one is ever relying on it?
Instead what I would need is an easy way to make sure that we control these destructor in the codebase as it evolves. For instance the attribute proposed above would be great, and having the opposite positive attribute (expressing that I intend a static destructor) would be even better. Especially coupled with a warning for every static destructor emitted, since I can then use -Werrror to force every global to be annotated (or locally disable the warning if I include third-party code).

On some embedded systems I’ve had a chance to work with, it’s quite trivial to be certain nobody is relying on them, because they don’t get executed. If anyone thought they were relying on them for their code…well, they were wrong. :slight_smile:

On some embedded systems I’ve had a chance to work with, it’s quite trivial to be certain nobody is relying on them, because they don’t get executed. If anyone thought they were relying on them for their code…well, they were wrong. :slight_smile:

Hi,

On this topic, I work on an embedded system where I have these concerns about static destructor, but I can’t either fully / blindly disable them. Disabling them with a global flag seems dangerous: how can I make sure that no-one is ever relying on it?
Instead what I would need is an easy way to make sure that we control these destructor in the codebase as it evolves. For instance the attribute proposed above would be great, and having the opposite positive attribute (expressing that I intend a static destructor) would be even better. Especially coupled with a warning for every static destructor emitted, since I can then use -Werrror to force every global to be annotated (or locally disable the warning if I include third-party code).

I’ll do the flag as well as the attribute, and review your patch for the warning? :wink:

I’ve got a few things on my plate at the moment, but I’ll get to this ~soon and will CC those who replied to this email in the review.

Fair :slight_smile:
To clarify: you’re adding two attributes (positive and negative)?

Thanks,

On some embedded systems I’ve had a chance to work with, it’s quite trivial to be certain nobody is relying on them, because they don’t get executed. If anyone thought they were relying on them for their code…well, they were wrong. :slight_smile:

Hi,

On this topic, I work on an embedded system where I have these concerns about static destructor, but I can’t either fully / blindly disable them. Disabling them with a global flag seems dangerous: how can I make sure that no-one is ever relying on it?
Instead what I would need is an easy way to make sure that we control these destructor in the codebase as it evolves. For instance the attribute proposed above would be great, and having the opposite positive attribute (expressing that I intend a static destructor) would be even better. Especially coupled with a warning for every static destructor emitted, since I can then use -Werrror to force every global to be annotated (or locally disable the warning if I include third-party code).

I’ll do the flag as well as the attribute, and review your patch for the warning? :wink:

Fair :slight_smile:

:heart:

To clarify: you’re adding two attributes (positive and negative)?

Yes.

I’m totally on board with a compiler flag.

But I still wonder a bit if we really need to add an attribute, considering that you can already effectively create a global which doesn’t run its destructor, by creating a library type which placement new’s into an aligned_storage member.

That works within today’s standard c++, without needing to add any extensions. Now – the main downside of the aligned_storage solution is that its constructor cannot be constexpr, even if the underlying type is, since placement new is not allowed in constexpr functions.

However, apparently the solution using a union will become correct in C++20, if P0784 is accepted:

template
union NoDestroy {
template <typename… Ts>
explicit constexpr NoDestroy(Ts&&…args) : val(std::forward(args)…) {}
explicit constexpr NoDestroy(T val) : val(val) {}
/* not valid at the moment:*/ constexpr ~NoDestroy() {}
T val;
};

ISTM that a type like that should probably be added to the standard library in C++20, too, but if it isn’t, users can write it themselves. (I’d also note that even in earlier C++ versions, you can get away with omitting the constexpr on the destructor, and your program will work fine. It is technically a violation of the standard to actually use the value after the empty no-op destructor is notionally called, but since it does not actually destroy the value contained in the union, it doesn’t in practice cause a problem.)

Actually it seems that the warning already exists (-Wexit-time-destructors).
Seems like it’ll need to be updated to handle your attribute though.

I’ve put a patch up for this here:

Thanks!

@bcardosolopes @jfb (@Erik_Pilkington1)
Sorry for replying to this old thread. I wonder whether there has been any update to https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1247r0.html https://github.com/cplusplus/papers/issues/291.

I noticed that there had not been a GCC feature request yet, so I filed
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114357

1 Like

It was seen in the C++ committee a while ago, and got positive support with feedback. It needs to be updated to be seen again. I’ll talk with @bcardosolopes about ti this week.

1 Like