0

What is the best way to end the lifetime of an object with static storage duration?

Current implementation finds the caller of __run_exit_handlers which then will be used to determine the __exit_funcs.

However this would easily fail since offset to __run_exit_handlers can change easily even in glibc with the same version. Another thing that could be done is to resolve the address of __run_exit_handlers first then use it in finding the caller rather than using a hardcoded call offset.

Current Working Code:

#include <iostream>
#include <fstream>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <cstdio>

#include <execinfo.h>

struct A
{
    A(std::string pName)
        : mName(pName)
    {
        std::printf("%s %s\n", __PRETTY_FUNCTION__, mName.c_str());
    }

    ~A()
    {
        std::printf("%s %s\n", __PRETTY_FUNCTION__, mName.c_str());
    }
    volatile int i = 0;
    std::string mName;
};

A a{"a"};
A b{"b"};
A c{"c"};

class StaticDestroyer
{
public:
    StaticDestroyer()
    {
        std::ifstream maps("/proc/self/maps", std::ios::in);
        char line[1024];
        uint8_t* magic = nullptr;
        while (maps.getline(line, sizeof(line)))
        {
            char perms[4];
            uint8_t *magicbegin, *magicend;
            std::string lsv(line);
            if (std::string::npos == lsv.find("/libc-",0,6)) continue;
            std::sscanf(line, "%lx-%lx %4s", &magicbegin, &magicend, perms);
            if (perms[0]==114 && perms[2]==120)
            {
                 magic = findMagic(magicbegin, magicend);
                 break;
            }
        }

        if (magic==nullptr)
            throw std::runtime_error("magic not found!");

        mHead = *(HakaishinNode**)magic;
    }

    bool destroy(void* pTarget)
    {
        HakaishinNode *current = mHead;
        while (nullptr != current)
        {
            for (size_t i = current->idx-1 ; i>0; i--)
            {
                const  Hakaishin *const f = &current->fns[i];
                if (4 == f->type && pTarget == f->arg)
                {
                    void (*destruct) (void *arg, int status) = f->fn;
                    asm ("ror $2*8+1, %0\nxor %%fs:%c2, %0" : "=r" (destruct) : "0" (destruct), "i" (48));
                    destruct (f->arg, 1);
                    if (current->idx-1 != i) for (size_t j = i; j < current->idx ; j++) current->fns[j] = current->fns[j+1];
                    current->idx--;
                    return true;
                }
            }

            current = current->next;
        }
        return false;
    }
private:
    struct Hakaishin
    {
        long int type;
        void (*fn) (void *arg, int status);
        void *arg;
        void *dso_handle;
    };

    struct HakaishinNode
    {
        struct HakaishinNode *next;
        size_t idx;
        Hakaishin fns[32];
    };

    uint8_t* findMagic(uint8_t* magicbegin, uint8_t* magicend)
    {
        const void* const begin = magicbegin;
        int32_t ptr;
        while ((magicbegin+7) <= magicend)
        {
            if (magicbegin[0]==0x48 && (magicbegin[1]==0x8b || magicbegin[1]==0x8d))
            {
                std::memcpy(&ptr, magicbegin+3, sizeof(ptr));
                uint8_t* magicp = magicbegin+ptr+7;
                if (ptr==0x38a5c1) return magicp;
            }
            magicbegin++;
        }
        return nullptr;
    }

    HakaishinNode* mHead = nullptr;
};

A& getA()
{
    static A a{"getA"};
    return a;
}

A& getA2()
{
    static A a{"getA2"};
    return a;
}

int main()
{
    std::printf("entering...\n");
    StaticDestroyer d;
    d.destroy(&a);
    d.destroy(&b);
    auto& ga = getA();
    d.destroy(&ga);
    getA2();
    std::printf("returning...\n");
}

Output:

A::A(std::string) a
A::A(std::string) b
A::A(std::string) c
entering...
A::~A() a
A::~A() b
A::A(std::string) getA
A::~A() getA
A::A(std::string) getA2
returning...
A::~A() getA2
A::~A() c
Quentin
  • 62,093
  • 7
  • 131
  • 191
Khal Buyo
  • 307
  • 1
  • 10
  • 4
    But why? The best way is not to do it at all, let the compiler do it. What you are doing is just strange. Don't do it like that, at least you could use `std::aligned_storage` with placement new and manage the lifetime manually. – KamilCuk Mar 13 '20 at 09:32
  • because you need to destruct a static object before exit? – Khal Buyo Mar 13 '20 at 09:34
  • No you don't. "Compiler" destroys static objects after exit. Did you try to just run `int main() { getA(); }` and see if `A::~A getA` is printed? For reference for example [std::exit](https://en.cppreference.com/w/cpp/utility/program/exit) `destructors of objects with static storage duration are called` – KamilCuk Mar 13 '20 at 09:34
  • I am aware that the compiler destroys static objects after exit, I am also aware that you can destroy static object before exit just like above. – Khal Buyo Mar 13 '20 at 09:36
  • If you must do this then you might want to consider [nifty counters](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter). – G.M. Mar 13 '20 at 09:39
  • 1
    Before you dig into the guts of your runtime, how about an `std::optional` that you can `reset()`? – Quentin Mar 13 '20 at 09:49
  • `just like above` you "can" but the question is: why would you do something like that in that way? – KamilCuk Mar 13 '20 at 09:50
  • std::optional is a great idea imho, but it's desctructor will still be called after exit. – Khal Buyo Mar 13 '20 at 10:03
  • You really should clarify why you don't want any destructor to be called (not even one of `std::unique_ptr` or `std::optional`) after the main is exited, otherwise, it is not really possible to answer your question in a meaningful way. – t.niese Mar 13 '20 at 20:01

2 Answers2

0

Static objects will be destructed with the termination of the program.

If you like to manage the resources don't make it static or use a static pointer. Here you can allocate and de-allocate the corresponding resources. This approach comes very close to a singleton, which is considered to be an anti pattern.

Conclusion: If you need to manage a resource don't make it static.

schorsch312
  • 5,553
  • 5
  • 28
  • 57
0

The need to mess around with the default behavior of life-time in such a way indicates that you have a design flaw in your application.

So you should either consider restructuring your program to not use globals. Or at least change the way how you handle the globals. So if you really need globals and release them earlier, then switch to unique_ptr:

#include <iostream>
#include <functional>
#include <memory>

struct A
{
    A(std::string pName)
        : mName(pName)
    {
        std::printf("%s %s\n", __PRETTY_FUNCTION__, mName.c_str());
    }

    ~A()
    {
        std::printf("%s %s\n", __PRETTY_FUNCTION__, mName.c_str());
    }
    volatile int i = 0;
    std::string mName;
};

auto a = std::make_unique<A>("a");
auto b = std::make_unique<A>("b");
auto c = std::make_unique<A>("c");

auto& getA()
{
    static auto a = std::make_unique<A>("getA");
    return a;
}

auto& getA2()
{
    static auto a = std::make_unique<A>("getA2");
    return a;
}

int main() {
    std::printf("entering...\n");

    a = nullptr;
    b = nullptr;
    c = nullptr;

    getA();
    getA2();

    getA() = nullptr;

    std::printf("returning...\n");
}

That way you can release the objects managed by the unique_ptr earlier, but they will be released on exit automatically if you don't set them to nullptr manually.

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • 1
    @KhalBuyo why is that relevant? In which case is that a problem for you? Could you clarify that in your question? As soon as you link against a library there might already be other objects with static lifetime duration (e.g. the `std::cout`, `std::cin`) that are deleted on exit, so if calling a destructor on exit is a problem in your application then it is important to mention why. – t.niese Mar 13 '20 at 10:04
  • @KhalBuyo If you want to avoid automatic destruction, use a raw pointer instead. – VLL Mar 13 '20 at 10:10
  • One reason to do this is to gracefully destroy static objects before calling `exec*` function. And the one reason among others to call 'exec*' is to pass control over stdin file descriptor to the child process. It is not a design flaw. – ZAB Aug 04 '21 at 10:28