2009-12-23 26 views
6

He portado mi aplicación de VC++ 7 a VC++ 9 recientemente. Ahora a veces se bloquea al salir: el tiempo de ejecución comienza a llamar a los destructores de objetos globales y se produce una infracción de acceso en uno de ellos.¿Qué significa "dinámico" en "destructor de atexit dinámico"?

Cada vez que el observador Pila de llamadas las funciones principales son:

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

La pregunta es ¿cuál es el significado de la palabra "dinámico" en "destructor atexit dinámica"? ¿Puede proporcionarme información adicional?

Respuesta

7

su difícil establecer claramente el problema exacto sin el código real, pero tal vez usted puede encontrar usted mismo después de leer esto:

de http://www.gershnik.com/tips/cpp.asp (enlace está muerto véase más adelante)

atexit() y bibliotecas dinámicas/compartidas

Las bibliotecas estándar C y C++ incluyen una función a veces útil: atexit(). Permite a la persona que llama registrar una devolución de llamada que se llamará cuando la aplicación se cierre (normalmente). En C++ también se integra con el mecanismo que llama a los destructores de objetos globales, por lo que las cosas que se crearon antes de una llamada dada a atexit() se destruirán antes de la devolución de llamada y viceversa. Todo esto debe ser conocido y funciona perfectamente bien hasta que las DLL o bibliotecas compartidas entren en la imagen.

El problema es, por supuesto, que las bibliotecas dinámicas tienen su propia vida útil que, en general, podría terminar antes de la aplicación principal. Si un código en una DLL registra una de sus propias funciones como una devolución de llamada atexit(), esta devolución de llamada debería ser llamada antes de que se descargue la DLL. De lo contrario, se producirá un bloqueo o algo peor durante la salida de la aplicación principal. (Para hacer cosas desagradables, los bloqueos durante la salida son notablemente difíciles de depurar, ya que muchos depuradores tienen problemas para lidiar con procesos de muerte).

Este problema es mucho más conocido en el contexto de los destructores de objetos globales de C++ (que, como se mencionó anteriormente, son hermanos de atexit()). Obviamente, cualquier implementación de C++ en una plataforma que admita bibliotecas dinámicas tuvo que lidiar con este problema y la solución unánime fue llamar a los destructores globales cuando la biblioteca compartida está descargada o cuando se cierra la aplicación, lo que ocurra primero.

Hasta ahora todo bien, excepto que algunas implementaciones "olvidaron" extender el mismo mecanismo al viejo atexit normal(). Como el estándar de C++ no dice nada acerca de las bibliotecas dinámicas, tales implementaciones son técnicamente "correctas", pero esto no ayuda al programador pobre que por una u otra razón necesita llamar a atexit() pasando una devolución de llamada que reside en un archivo DLL.

En las plataformas sé que la situación es la siguiente. MSVC en Windows, GCC en Linux y Solaris y SunPro en Solaris tienen un atexit "correcto"() que funciona del mismo modo que los destructores globales. Sin embargo, GCC en FreeBSD en el momento de escribir este documento tiene un "roto" que siempre registra devoluciones de llamadas para ser ejecutadas en la aplicación en lugar de salida de biblioteca compartida. Sin embargo, como se prometió, los destructores globales funcionan bien incluso en FreeBSD.

¿Qué debe hacer en el código portátil? Una solución es, por supuesto, evitar atexit() por completo. Si necesita su funcionalidad es fácil sustituirlo por destructores de C++ de la siguiente manera

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

Esto funciona a expensas de escribir mucho y la interfaz no intuitivo.Es importante tener en cuenta que no hay pérdida de funcionalidad en comparación con la versión atexit(). El destructor de devolución de llamada no puede lanzar excepciones, pero también lo hacen las funciones invocadas por atexit. La función callback :: register() puede no ser segura para subprocesos en una plataforma dada, pero también lo es atexit() (C++ estándar actualmente no se reproduce en los subprocesos, por lo que implementar el atexit() de manera segura depende de la implementación)

¿Qué sucede si quiere evitar todo lo anterior? Usualmente hay una forma y depende de un simple truco. En lugar de llamar a atexit roto() tenemos que hacer lo que haga el compilador de C++ para registrar destructores globales. Con GCC y otros compiladores que implementan el llamado Itanium ABI (ampliamente utilizado para plataformas que no son Itanium), el conjuro mágico se llama __cxa_atexit. Aquí es cómo usarlo. Primero coloque el código a continuación en un encabezado de utilidad

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

La forma en que __cxa_atexit funciona es la siguiente. Registra la devolución de llamada en una sola lista global de la misma forma que lo hace el atexit() no compatible con DLL. Sin embargo, también asocia los otros dos parámetros con él. El segundo parámetro es algo agradable de tener. Permite que la devolución de llamada se transmita en algún contexto (como en el caso de algunos objetos) y, por lo tanto, se puede reutilizar una sola devolución de llamada para realizar varias limpiezas. El tercer parámetro es el que realmente necesitamos. Es simplemente una "cookie" que identifica la biblioteca compartida que debe asociarse con la devolución de llamada. Cuando se descarga una biblioteca compartida, su código de limpieza atraviesa la lista de devolución de llamada atexit y llama (y elimina) cualquier devolución de llamada que tenga una cookie que coincida con la asociada a la biblioteca que se está descargando. ¿Cuál debería ser el valor de la cookie? No es la dirección de inicio DLL y no su manejador dlopen() como uno podría suponer. En cambio, el identificador se almacena en una variable global especial __dso_handle mantenida por C++ en tiempo de ejecución.

La función safe_atexit debe estar en línea. De esta forma selecciona cualquier cosa que use el módulo de llamada __dso_handle, que es exactamente lo que necesitamos.

¿Debería utilizar este enfoque en lugar de uno detallado y más portátil? Probablemente no, aunque quién sabe qué requisitos podría tener. Aún así, incluso si nunca lo usa, es útil conocer cómo funcionan las cosas, por eso es que está incluido aquí.

+0

¿Esto implica que "dinámico" es de "biblioteca de carga dinámica"? – sharptooth

+0

No, el término se refiere al registro dinámico de la devolución de llamada de la función atexit durante el tiempo de ejecución, que es opuesto al registro estático realizado en tiempo de compilación. es útil para bibliotecas de carga dinámica ya que puede eliminar una función de devolución de llamada de la lista atexit si en algún punto arbitrario su aplicación decide descargar un dll previamente cargado, en ese caso también puede llamar manualmente cualquier código de limpieza sin necesidad de retransmitir ateixt. – Alon

+0

enlace está muerto. Debe considerar copiar la información relevante en su respuesta. –