【问题标题】:Best Way To Destruct Global And Static Objects?破坏全局和静态对象的最佳方法?
【发布时间】:2020-03-13 09:25:30
【问题描述】:

结束具有静态存储持续时间的对象的生命周期的最佳方法是什么?

当前实现找到__run_exit_handlers 的调用者,然后将用于确定__exit_funcs

但是这很容易失败,因为即使在具有相同版本的 glibc 中,__run_exit_handlers 的偏移量也可以轻松更改。可以做的另一件事是先解析__run_exit_handlers 的地址,然后使用它来查找调用者,而不是使用硬编码的调用偏移量。

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");
}

输出:

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

【问题讨论】:

  • 但是为什么呢?最好的办法是根本不做,让编译器去做。你在做什么很奇怪。不要那样做,至少您可以将std::aligned_storage 与placement new 一起使用并手动管理生命周期。
  • 因为你需要在退出前销毁一个静态对象?
  • 不,你没有。 “编译器”在退出后销毁静态对象。您是否尝试运行int main() { getA(); } 并查看是否打印了A::~A getA?供参考,例如std::exitdestructors of objects with static storage duration are called
  • 我知道编译器在退出后会销毁静态对象,我也知道您可以像上面一样在退出前销毁静态对象。
  • 在深入了解运行时的内容之前,您可以使用std::optional&lt;A&gt; 来使用reset() 吗?

标签: c++ static global glibc


【解决方案1】:

静态对象将随着程序的终止而被破坏。

如果您想管理资源,请不要将其设为静态或使用静态指针。在这里您可以分配和取消分配相应的资源。这种方法非常接近单例,被认为是anti pattern

结论: 如果您需要管理资源,请不要将其设为静态。

【讨论】:

    【解决方案2】:

    需要以这种方式处理生命周期的默认行为表明您的应用程序存在设计缺陷。

    因此,您应该考虑重组程序以不使用全局变量。或者至少改变你处理全局变量的方式。所以如果你真的需要全局变量并提前发布,那就切换到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");
    }
    

    这样你可以提前释放unique_ptr管理的对象,但是如果你不手动设置它们为nullptr,它们会在退出时自动释放。

    【讨论】:

    • std::unique_ptr&lt;A&gt;::~unique_ptr 在退出期间仍将被调用。你怎么能删除它?
    • @KhalBuyo 为什么这有关系?在哪种情况下这对您来说是个问题?你能在你的问题中澄清一下吗?一旦你链接到一个库,可能已经有其他具有静态生命周期的对象(例如std::coutstd::cin)在退出时被删除,所以如果在退出时调用析构函数是你的应用程序中的一个问题,那么它重要的是要说明原因。
    • @KhalBuyo 如果您想避免自动销毁,请改用原始指针。
    • 这样做的一个原因是在调用exec* 函数之前优雅地销毁静态对象。调用“exec*”的一个原因是将标准输入文件描述符的控制权传递给子进程。这不是设计缺陷。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-31
    • 1970-01-01
    • 2011-09-26
    • 1970-01-01
    • 2018-02-26
    • 1970-01-01
    相关资源
    最近更新 更多