https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117630

            Bug ID: 117630
           Summary: Useless atexit entry for generic_category_instance and
                    system_category_instance
           Product: gcc
           Version: 14.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: rdiez-2006 at rd10 dot de
  Target Milestone: ---

I have this embedded firmware project of mine, which uses Newlib:

https://github.com/rdiez/DebugDue

It is the template for other similar bare-metal projects I have. Even though
some targets have very little SRAM (like 16 KiB), I am still using C++ and
exceptions. The project above documents how I configure GCC to that effect.

Up until GCC 13, I have been doing this check:

  if ( _GLOBAL_ATEXIT != nullptr )
  {
    Panic( "Unexpected entries in atexit table." );
  }

On devices with very little RAM and without an operating system, initialisation
and destruction can be tricky. With the check above, I am making sure that
nothing unexpected comes up in that respect. I am initialising all static
objects manually anyway, to prevent any ordering surprises (the initialisation
order of C++ static objects can be problematic too).

The check above fails with GCC 14.2. Apparently, 2 destructors for
generic_category_instance and system_category_instance are generated as atexit
entries.

Because of the way that Newlib works, that wastes 400 bytes of SRAM, which
corresponds to sizeof( _global_atexit0 ). The structure has room for 32 atexit
calls (because of some ANSI conformance), but my project is only using 2
entries for those destructors. It is not just a RAM wastage issue, the main
issue is that it interferes with my automatic method of detecting unexpected
destruction surprises.

This is very similar to this issue I reported 2 years ago, which got fixed for
GCC 12.4:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107500

In this occasion, the interesting code lives here:

gcc-14.2.0/libstdc++-v3/include/std/system_error
gcc-14.2.0/libstdc++-v3/src/c++11/system_error.cc

The code tries to make those 2 instances immortal with a union trick, see
"struct constant_init". GCC still fails to optimise away such empty destructors
(this has been reported some years ago) and generates useless atexit() entries
for them.

The interesting thing is that base class std::error_category and derived
classes generic_error_category and system_error_category seem to be empty of
any data members. There isn't anything to destroy anyway. I am guessing that
the C++ standard requires these objects to be static, and libstdc++ has to
revert to ugly tricks because of C++'s well-known "static initialization order
fiasco".

This std::error_category area is complex, I am out of my depth here.

As far as I can tell, there are other derived classes like
future_error_category and iostream_category, but only generic_category_instance
and system_category_instance cause problems in my project. Routine
iostream_category() uses a similar union trick, but does not come up. I am
guessing the reason is that my project does not use iostream at all.

Is there a way I can tell who is using system_category() and
generic_category()? This is surprising for me, as my bare metal project has no
OS and barely uses any errno. There isn't even support for FILE and the like.
The code base actually uses little C++ trickery. I didn't even know that error
categories was a thing. Perhaps there is some standard C++ feature which I can
disable, in order to stop usage of those 2 error categories.

I could patch GCC when building the toolchain. Does anybody have any idea how
to patch system_category etc. away, without breaking too much?

Reply via email to