Visual studio 2008 什么是;“动态”;在;动态atexit析构函数“;什么意思?

Visual studio 2008 什么是;“动态”;在;动态atexit析构函数“;什么意思?,visual-studio-2008,debugging,visual-c++,Visual Studio 2008,Debugging,Visual C++,最近我将我的应用程序从VC++7移植到了VC++9。现在,它有时在退出时崩溃——运行时开始调用全局对象析构函数,其中一个发生访问冲突 每当我观察调用堆栈时,顶部函数是: CMyClass::~CMyClass() <- crashes here dynamic atexit destructor for 'ObjectName' _CRT_INIT() some more runtime-related functions follow CMyClass::~CMyClass() 来自(

最近我将我的应用程序从VC++7移植到了VC++9。现在,它有时在退出时崩溃——运行时开始调用全局对象析构函数,其中一个发生访问冲突

每当我观察调用堆栈时,顶部函数是:

CMyClass::~CMyClass() <- crashes here
dynamic atexit destructor for 'ObjectName'
_CRT_INIT()
some more runtime-related functions follow

CMyClass::~CMyClass()

来自(链接现在已关闭,请参见下文)

atexit()和动态/共享库

C和C++标准库包括一个有时有用的函数:它允许调用者注册一个回调,该回调将在应用程序退出时被调用(通常)。在C++中,它也与调用全局对象析构函数的机制集成,这样在给定调用到ATEXIT()之前创建的事物在回调之前就会被破坏,反之亦然。所有这些都应该是众所周知的,在DLL或共享库出现之前,它工作得非常好

当然,问题是动态库有自己的生命周期,一般来说,生命周期可能在主应用程序的生命周期之前结束。如果DLL中的代码将自己的函数之一注册为atexit()回调,则最好在卸载DLL之前调用此回调。否则,在主应用程序退出期间将发生崩溃或更糟的情况。(由于许多调试器在处理即将死亡的进程时都有问题,所以在退出时很难调试出令人讨厌的崩溃)

这个问题在C++全局对象的析构函数(如上面提到的是AtExt()的兄弟)中更为人所知。显然,支持动态库的平台上的任何C++实现都必须处理这个问题,一致的解决方案是在共享库卸载或应用程序退出时调用全局析构函数,无论哪个都是最先出现的。 到目前为止还不错,只是有些实现“忘记”将相同的机制扩展到普通的旧atexit()。由于C++标准没有对动态库进行任何说明,所以这些实现在技术上是“正确的”,但这并不能帮助那些由于某种原因调用ATEXIT()的程序员,而传递一个驻留在DLL中的回调。 在平台上,我了解的情况如下。Windows上的MSVC、Linux上的GCC和Solaris上的SunPro都有一个“正确”的atexit(),其工作方式与全局析构函数相同。然而,在撰写本文时,FreeBSD上的GCC有一个“坏的”回调,它总是注册要在应用程序上执行的回调,而不是共享库出口。然而,正如承诺的那样,全局析构函数即使在FreeBSD上也能正常工作

在可移植代码中应该做什么?当然,一种解决方案是完全避免atexit()。如果需要它的功能,用下面的方式

很容易用C++析构函数替换它
//Code with atexit()

void callback()
{
    //do something
}

...
atexit(callback);
...

//Equivalent code without atexit()

class callback
{
public: 
    ~callback()
    {
        //do something
    }

    static void register();
private:
    callback()
    {}

    //not implemented
    callback(const callback &);
    void operator=(const callback &); 
};

void callback::register()
{
    static callback the_instance;
}

...
callback::register();
...
这样做的代价是大量的输入和非直观的界面。需要注意的是,与atexit()版本相比,没有功能损失。回调析构函数不能抛出异常,但atexit调用的函数也不能抛出异常。在给定的平台上,callback::register()函数可能不是线程安全的,但atexit()也是线程安全的(C++标准目前对线程不起作用,因此是否以线程安全的方式实现atexit()取决于实现)

如果你想避免上面所有的打字怎么办?通常有一种方法,它依赖于一个简单的技巧。我们不需要调用中断的AtExtEt(),而是需要做C++编译器做的任何事情来登记全局析构函数。对于GCC和其他实现所谓安腾ABI(广泛用于非安腾平台)的编译器,这种神奇的咒语被称为u cxa_atexit。下面是如何使用它。首先将下面的代码放在一些实用程序标题中

#if defined(_WIN32) || defined(LINUX) || defined(SOLARIS)

    #include <stdlib.h>

    #define SAFE_ATEXIT_ARG 

    inline void safe_atexit(void (*p)(SAFE_ATEXIT_ARG)) 
    {
        atexit(p);
    }

#elif defined(FREEBSD)

    extern "C" int __cxa_atexit(void (*func) (void *), void * arg, void * dso_handle);
    extern "C" void * __dso_handle;     


    #define SAFE_ATEXIT_ARG void *

    inline void safe_atexit(void (*p)(SAFE_ATEXIT_ARG))
    {
        __cxa_atexit(p, 0, __dso_handle);
    }

#endif
And then use it as follows


void callback(SAFE_ATEXIT_ARG)
{
    //do something
}

...
safe_atexit(callback);
...
#如果已定义(_WIN32)|已定义(LINUX)|已定义(SOLARIS)
#包括
#定义安全退出参数
内联void safe_atexit(void(*p)(safe_atexit_ARG))
{
脱欧(p);
}
#定义的elif(FREEBSD)
外部“C”int uu cxa u atexit(void(*func)(void*)、void*arg、void*dso\u句柄);
外部“C”无效*\u dso\u句柄;
#定义安全退出参数void*
内联void safe_atexit(void(*p)(safe_atexit_ARG))
{
__cxa_atexit(p,0,dso_handle);
}
#恩迪夫
然后按如下方式使用它
无效回调(安全退出参数)
{
//做点什么
}
...
安全退出(回调);
...
cxa-atexit的工作方式如下。它在一个全局列表中注册回调,与不支持DLL的atexit()一样。但是,它还将其他两个参数与之关联。第二个参数只是一个好东西。它允许将回调传递给某个上下文(比如某个对象的this),因此可以将单个回调重新用于多次清理。第三个参数是我们真正需要的参数。它只是一个“cookie”,用于标识应该与回调关联的共享库。卸载任何共享库时,其清理代码将遍历atexit回调列表,并调用(并删除)具有与正在卸载的库关联的cookie相匹配的cookie的所有回调。cookie的值应该是多少?它不是DLL开始地址,也不是人们可能认为的dlopen()句柄。相反,句柄被存储在C++运行时维护的一个特殊的全局变量yxdSo句柄中。 safe_atexit函数必须是内联的。通过这种方式,它选择调用模块使用的任何dso句柄,这正是我们所需要的

您是否应该使用这种方法,而不是上面冗长且更易于移植的方法?可能不会,但谁知道您可能有什么要求。尽管如此,即使您从未使用过它,了解事物是如何工作的也是有帮助的,这就是为什么将它包括在这里。

这是否意味着“