最近,我将我的应用程序从VC++7移植到了VC++9。现在它有时会在退出时崩溃-运行时开始调用全局对象析构函数,并且其中一个发生访问冲突。
每当我观察调用堆栈时,顶级函数是:
CMyClass::~CMyClass() <- crashes here
dynamic atexit destructor for 'ObjectName'
_CRT_INIT()
some more runtime-related functions follow问题是"dynamic atexit destructor“中"dynamic”这个词的意思是什么?它能为我提供任何额外的信息吗?
发布于 2009-12-23 20:22:08
没有实际的代码很难找出确切的问题,但也许你可以在阅读本文后自己找到它:
来自http://www.gershnik.com/tips/cpp.asp
atexit()和动态/共享库
C和C++标准库包含一个有时很有用的函数: atexit()。它允许调用者注册一个回调,该回调将在应用程序退出时(通常)被调用。在C++中,它还集成了调用全局对象的析构函数的机制,因此在给定的atexit()调用之前创建的东西将在回调之前销毁,反之亦然。所有这一切都应该是众所周知的,而且在All或共享库进入之前,它都工作得很好。
当然,问题是动态库有它们自己的生命周期,通常可以在主应用程序的生命周期之前结束。如果DLL中的代码将其自己的函数之一注册为atexit()回调,则最好在卸载DLL之前调用此回调。否则,在主应用程序退出期间将发生崩溃或更糟糕的情况。(为了让事情变得糟糕,在退出期间崩溃是出了名的难以调试,因为许多调试器在处理死亡进程时都有问题)。
这个问题在C++全局对象的析构函数(如上所述,它们是C++ ()的兄弟)的上下文中更为人所知。显然,在支持动态库的平台上的任何C++实现都必须处理这个问题,一致的解决方案是在卸载共享库时或在应用程序退出时调用全局析构函数,无论哪个先来。
到目前为止还不错,除了一些实现“忘记”将相同的机制扩展到普通的老式atexit()。因为C++标准没有提到任何关于动态库的东西,所以这样的实现在技术上是“正确的”,但是这对那些因为某种原因需要调用atexit()来传递驻留在DLL中的回调的可怜的程序员没有任何帮助。
在平台上,我所了解的情况如下。Windows上的MSVC、Linux和Solaris上的GCC以及Solaris上的SunPro都有一个与全局析构函数相同的"right“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()取决于实现)
如果你想避免上面所有的输入,该怎么办?通常有一种方法,它依赖于一个简单的技巧。我们需要做C++编译器所做的注册全局析构函数的工作,而不是调用被破坏的atexit()。对于GCC和其他实现所谓的Itanium ABI (广泛用于非Itanium平台)的编译器,这个魔术被称为__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);
...__cxa_atexit的工作方式如下所示。它将回调注册到一个全局列表中,方法与非DLL感知的atexit()相同。但是,它还将其他两个参数与其关联。第二个参数是一个很好的东西。它允许向回调传递一些上下文(比如某个对象的this),因此一个回调可以重用于多个清理。第三个参数是我们真正需要的。它只是一个标识应该与回调关联的共享库的"cookie“。当卸载任何共享库时,其清理代码将遍历atexit回调列表,并调用(并删除)任何具有与正在卸载的库相匹配的cookie的回调。cookie的价值应该是什么?它不是DLL起始地址,也不是人们可能假设的dlopen()句柄。相反,句柄存储在由C++运行时维护的特殊全局变量__dso_handle中。
safe_atexit函数必须是内联的。通过这种方式,它选择调用模块使用的任何__dso_handle,这正是我们所需要的。
您是否应该使用此方法,而不是上面的冗长和更可移植的方法?也许不会,但谁知道你会有什么样的需求。尽管如此,即使你从来没有使用过它,了解它是如何工作的也是有帮助的,所以这就是为什么它被包含在这里。
https://stackoverflow.com/questions/1952467
复制相似问题