Let's recap that.

I wanted to control the order of constructors mostly to control the order
of destructors (which should be inverse).

Then a non-GlobalPtr/-InitInstance object allocating memory would not cause
a crash if the memory manager is destroyed before it.

It looks like there is another way to make that.

std::atexit (https://en.cppreference.com/w/cpp/utility/program/atexit) docs
says:

"The functions will be called during the destruction of the static objects,
in reverse order: if A was registered before B, then the call to B is made
before the call to A. Same applies to the ordering between static object
constructors and the calls to atexit: see std::exit
<https://en.cppreference.com/w/cpp/utility/program/exit>"

And std::exit (https://en.cppreference.com/w/cpp/utility/program/exit) says:

"1) destructors of objects with static storage duration are called in
reverse order of completion of their constructors or the completion of their
 dynamic initialization
<https://en.cppreference.com/w/cpp/language/initialization#Dynamic_initialization>,
and the functions passed to std::atexit
<https://en.cppreference.com/w/cpp/utility/program/atexit> are called in
reverse order they are registered (last one first).
a) any static objects whose initialization was completed before the call to
std::atexit <https://en.cppreference.com/w/cpp/utility/program/atexit> for
some function F will be destroyed after the call to F during program
termination.
b) any static objects whose construction began after the call to std::atexit
<https://en.cppreference.com/w/cpp/utility/program/atexit> for some
function F will be destroyed before the call to F during program
termination (this includes the case where std::atexit
<https://en.cppreference.com/w/cpp/utility/program/atexit> was called from
the constructor of the static object)"

So a static storage object allocating memory with the default pool will
call the allocator before it's constructor begins (and of course,
completes) so if the allocator registers a atexit handler for cleanup, that
handler will be called after the object destructor.

I did a test and result in Linux (clang and gcc), Windows (VS 2017 and
2019) and MacOS (clang) is identical:

// a.cpp
#include <cstdio>

extern void fb();

struct CA
{
        CA(int n)
                : n(n)
        {
                printf("CA(%d)\n", n);

                if (n == 2)
                        fb();
        }

        ~CA()
        {
                printf("~CA(%d)\n", n);
        }

        int n;
};

static CA ca1(1);
static CA ca2(2);
static CA ca3(3);

int main()
{
        printf("main\n");
        return 0;
};

----

// b.cpp
#include <cstdio>
#include <cstdlib>

struct CB
{
        CB(int n)
                : n(n)
        {
                printf("CB(%d)\n", n);
        }

        ~CB()
        {
                printf("~CB(%d)\n", n);
        }

        void f()
        {
                printf("CB::f(%d)\n", n);
        }

        int n;
};

static CB cb1(1);
static CB cb2(2);

static void fexit()
{
        printf("fexit\n");
}

void fb()
{
        printf("fb\n");
        atexit(fexit);
}

---

result:
CA(1)
CA(2)
fb
CA(3)
CB(1)
CB(2)
main
~CB(2)
~CB(1)
~CA(3)
~CA(2)
fexit
~CA(1)


Adriano


On Wed, Apr 14, 2021 at 2:25 PM Adriano dos Santos Fernandes <
adrian...@gmail.com> wrote:

> Hi!
>
> C++ guarantees the execution order of constructors only inside the same
> translation unit (say, a .cpp file) for global objects.
>
> It also guarantees inverse order of destructors in relation to
> constructors order.
>
> But constructor order across translation units are undefined.
>
> We used to have problems with that.
>
> Then things like InitInstance and GlobalPtr were born.
>
> As I understand, in "common" (not Firebird code) C++, usage of
> new/malloc on library code not paired with delete/free becomes memory
> leaks when that library are unloaded.
>
> But in Firebird these goes to default pool with are deallocated at
> library exit.
>
> It's good thing, but it's were the problem starts.
>
> I was doing some tests integrating Boost.Test library in Firebird code.
> And then it segfaults at exit.
>
> It uses global std objects which in this case have theirs destructors
> called after Firebird default pool are already cleaned up.
>
> In current code, we have re2 C++ library statically linked.
>
> It looks like it works because it does not have global objects, i.e., we
> rely on re2 implementation details.
>
> I was looking at better way to solve this problem.
>
> GCC/clang has init_priority attribute:
> https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html
>
> It's not well documented. Here we see some implementation details about
> default value:
> https://gcc.gnu.org/legacy-ml/gcc-help/2015-08/msg00027.html
>
> I have tested with common/classes/init.cpp and this solved the specific
> problem I had with Boost.Test:
>
> Cleanup global __attribute__ ((init_priority (2000)));
>
> For MSVC I didn't tested, but found this docs with probably could be
> used for similar behavior:
>
>
> https://docs.microsoft.com/en-us/cpp/preprocessor/init-seg?redirectedfrom=MSDN&view=msvc-160
>
>
> Adriano
>
Firebird-Devel mailing list, web interface at 
https://lists.sourceforge.net/lists/listinfo/firebird-devel

Reply via email to