gpt4 book ai didi

visual-studio-2008 - "dynamic"中的 "dynamic atexit destructor"是什么意思?

转载 作者:行者123 更新时间:2023-12-04 14:07:23 27 4
gpt4 key购买 nike

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

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

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

问题是“动态atexit析构函数”中的“动态”一词是什么意思?它可以向我提供任何其他信息吗?

最佳答案

在没有实际代码的情况下很难查明确切的问题,但也许您可以在阅读本文后自己找到它:

来自 http://www.gershnik.com/tips/cpp.asp (链接现已失效,见下文)

atexit() 和动态/共享库

C 和 C++ 标准库包括一个有时有用的函数:atexit()。它允许调用者注册一个回调,该回调将在应用程序退出(通常)时调用。在 C++ 中,它还与调用全局对象的析构函数的机制集成在一起,因此在给定调用 atexit() 之前创建的东西将在回调之前被销毁,反之亦然。所有这些都应该是众所周知的,并且在 DLL 或共享库进入图片之前它可以正常工作。

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

这个问题在 C++ 全局对象(如上所述,它们是 atexit() 的兄弟)的析构函数的上下文中更为人所知。显然,支持动态库的平台上的任何 C++ 实现都必须处理这个问题,一致的解决方案是在共享库卸载或应用程序退出时调用全局析构函数,以先到者为准。

到目前为止一切顺利,除了一些实现“忘记”将相同的机制扩展到普通的旧 atexit()。由于 C++ 标准没有说明关于动态库的任何内容,因此此类实现在技术上是“正确的”,但这对出于某种原因需要调用 atexit() 传递驻留在 DLL 中的回调的可怜程序员没有帮助。

在平台上我了解的情况如下。 Windows 上的 MSVC、Linux 和 Solaris 上的 GCC 以及 Solaris 上的 SunPro 都有一个“正确的” atexit() ,其工作方式与全局析构函数相同。然而,在撰写本文时,FreeBSD 上的 GCC 有一个“损坏”的 GCC,它总是注册要在应用程序上执行的回调,而不是共享库退出。然而,正如 promise 的那样,全局析构函数即使在 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()。对于实现所谓 Itanium ABI(广泛用于非 Itanium 平台)的 GCC 和其他编译器,魔法咒语称为 __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 的任何回调。 cookie 的值应该是多少?它不是 DLL 的起始地址,也不是人们想象的 dlopen() 句柄。相反,句柄存储在由 C++ 运行时维护的特殊全局变量 __dso_handle 中。

safe_atexit 函数必须是内联的。通过这种方式,它选择调用模块使用的任何 __dso_handle,这正是我们需要的。

您应该使用这种方法而不是上面冗长且更便携的方法吗?可能不会,但谁知道您可能有什么要求。尽管如此,即使您从未使用过它,了解它是如何工作的也是有帮助的,因此这就是将其包含在此处的原因。

关于visual-studio-2008 - "dynamic"中的 "dynamic atexit destructor"是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1952467/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com