什么是“动态”在“动态atexit析构函数”意思? [英] What does "dynamic" in "dynamic atexit destructor" mean?

查看:185
本文介绍了什么是“动态”在“动态atexit析构函数”意思?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

  CMyClass ::〜CMyClass()<  - 崩溃在这里
动态atexit析构函数'ObjectName'
_CRT_INIT()
一些更多与运行时相关的功能跟随

问题是什么动态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中的回调的可怜的程序员。 / p>

在平台上我知道的情况如下。 Windows上的MSVC,Linux和Solaris上的GCC以及Solaris上的SunPro都具有正确atexit(),与全局析构函数的工作方式相同。然而,在撰写本文时,FreeBSD上的GCC有一个破坏的,它始终注册要在应用程序上执行的回调,而不是共享库退出。然而,如所承诺的那样,即使在FreeBSD上,全局析构函数也能正常工作。



在便携式代码中应该怎么办?当然,一个解决方案是完全避免atexit()。如果你需要它的功能,很容易用下面的方式用C ++析构函数来替换它:

  //使用atexit()$的代码b 
$ b void callback()
{
//做某事
}

...
atexit(回调);
...

//等价代码没有atexit()

类回调
{
public:
〜回调()
{
//做某事
}

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

//未实现
回调(const callback&);
void operator =(const callback&);
};

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

...
callback :: register();
...

这是以牺牲大量打字和非直观界面为代价的。重要的是要注意,与atexit()版本相比,没有功能的损失。回调析构函数不能抛出异常,而是由atexit调用的函数。 callback :: register()函数在一个给定的平台上可能不是线程安全的,而且atexit()(C ++标准当前在线程上是静默的,所以是否以线程安全的方式实现atexit()是由实现的) / p>

如果你想避免所有的打字上面怎么办?通常有一种方法,它依赖于一个简单的技巧。而不是调用broken atexit(),我们需要做任何C ++编译器注册全局析构函数。使用GCC和其他实现所谓的Itanium ABI(广泛应用于非Itanium平台)的编译器,魔法咒语被称为__cxa_atexit。这是如何使用它。首先将代码放在某些实用程序头部

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

#include< stdlib.h>

#define SAFE_ATEXIT_ARG

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


#elif defined(FREEBSD)

externCint __cxa_atexit(void(* func)(void *),void * arg,void * dso_handle);
externCvoid * __dso_handle;


#define SAFE_ATEXIT_ARG void *

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

#endif
然后使用它如下


void callback(SAFE_ATEXIT_ARG)
{
//做某事
}

...
safe_atexit(回调);
...

__cxa_atexit的工作方式如下。它将回调记录在单个全局列表中,与非DLL感知atexit()相同。然而,它还将其他两个参数与之相关联。第二个参数只是一个很好的东西。它允许回调传递一些上下文(像一些对象的这样),因此单个回调可以重用于多次清理。第三个参数是我们真正需要的。它只是一个cookie,用于标识应该与回调关联的共享库。卸载任何共享库时,其清理代码遍历atexit回调列表,并调用(并删除)具有与卸载库相关联的Cookie的任何回调。什么是cookie的价值?它不是DLL的起始地址,而不是它的dlopen()句柄,可以假设。相反,句柄存储在由C ++运行时维护的特殊全局变量__dso_handle中。



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



应该使用这种方法,而不是上面的冗长和更便携的方法?可能不是,虽然谁知道你可能有什么要求。不过,即使你没有使用它,它有助于了解事情的运作,所以这就是为什么包括在这里。


I ported my application from VC++7 to VC++9 recently. Now it sometimes crashes at exit - the runtime starts calling global objects destructors and an access violation occurs in one of them.

Whenever I observer the call stack the top functions are:

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

The question is what is the meaning of the word "dynamic" in "dynamic atexit destructor"? Can it provide any additional information to me?

解决方案

its difficult to pinpoint the exact problem without the actual code, but maybe you can find it yourself after reading this:

from http://www.gershnik.com/tips/cpp.asp (link is now dead see below)

atexit() and dynamic/shared libraries

C and C++ standard libraries include a sometimes useful function: atexit(). It allows the caller to register a callback that is going to be called when the application exits (normally). In C++ it is also integrated with the mechanism that calls destructors of global objects so things that were created before a given call to atexit() will be destroyed before the callback and vice versa. All this should be well known and it works perfectly fine until DLLs or shared libraries enter the picture.

The problem is, of course, that dynamic libraries have their own lifetime that, in general, could end before the main application's one. If a code in a DLL registers one of its own functions as an atexit() callback this callback should better be called before the DLL is unloaded. Otherwise, a crash or something worse will happen during the main application exit. (To make things nasty crashes during exit are notoriously hard to debug since many debuggers have problem dealing with dying processes).

This problem is much better known in the context of the destructors of C++ global objects (which, as mentioned above, are atexit()'s brothers). Obviously any C++ implementation on a platform that supports dynamic libraries had to deal with this issue and the unanimous solution was to call the global destructors either when the shared library is unloaded or on application exit, whichever comes first.

So far so good, except that some implementations "forgot" to extend the same mechanism to the plain old atexit(). Since C++ standard doesn't say anything about dynamic libraries such implementations are technically "correct", but this doesn't help the poor programmer who for one reason or another needs to call atexit() passing a callback that resides in a DLL.

On the platforms I know about the situation is as follows. MSVC on Windows, GCC on Linux and Solaris and SunPro on Solaris all have a "right" atexit() that works the same way as global destructors. However, GCC on FreeBSD at the time of this writing has a "broken" one which always registers callbacks to be executed on the application rather than shared library exit. However, as promised, the global destructors work fine even on FreeBSD.

What should you do in portable code? One solution is, of course, to avoid atexit() completely. If you need its functionality it is easy to replace it with C++ destructors in the following way

//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();
...

This works at the expense of much typing and non-intuitive interface. It is important to note that there is no loss of functionality compared to atexit() version. The callback destructor cannot throw exceptions but so do functions invoked by atexit. The callback::register() function may be not thread safe on a given platform but so is atexit() (C++ standard is currently silent on threads so whether to implement atexit() in a thread-safe manner is up to implementation)

What if you want to avoid all the typing above? There usually is a way and it relies on a simple trick. Instead of calling broken atexit() we need to do whatever the C++ compiler does to register global destructors. With GCC and other compilers that implements so-called Itanium ABI (widely used for non Itanium platforms) the magic incantation is called __cxa_atexit. Here is how to use it. First put the code below in some utility header

#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);
...

The way __cxa_atexit works is as follows. It registers the callback in a single global list the same way non-DLL aware atexit() does. However it also associates the other two parameters with it. The second parameter is just a nice to have thing. It allows the callback to be passed some context (like some object's this) and so a single callback can be reused for multiple cleanups. The third parameter is the one we really need. It is simply a "cookie" that identifies the shared library that should be associated with the callback. When any shared library is unloaded its cleanup code traverses the atexit callback list and calls (and removes) any callbacks that have a cookie that matches the one associated with the library being unloaded. What should be the value of the cookie? It is not the DLL start address and not its dlopen() handle as one might assume. Instead the handle is stored in a special global variable __dso_handle maintained by C++ runtime.

The safe_atexit function must be inline. This way it picks whatever __dso_handle is used by the calling module which is exactly what we need.

Should you use this approach instead of the verbose and more portable one above? Probably not, though who knows what requirements you might have. Still, even if you don't ever use it, it helps to be aware of how things work so this is why it is included here.

这篇关于什么是“动态”在“动态atexit析构函数”意思?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆