la sua difficile individuare il problema esatto, senza il codice vero e proprio, ma forse si possono trovare da soli dopo aver letto questo:
da http://www.gershnik.com/tips/cpp.asp (link ora è morto vedi sotto)
atexit() e librerie dinamiche/condivise
Le librerie standard C e C++ includono una funzione a volte utile: atexit(). Consente al chiamante di registrare una richiamata che verrà chiamata quando l'applicazione esce (normalmente). In C++ è anche integrato con il meccanismo che chiama i distruttori di oggetti globali, quindi le cose che sono state create prima di una determinata chiamata atextex() verranno distrutte prima della richiamata e viceversa. Tutto questo dovrebbe essere ben noto e funziona perfettamente fino a quando DLL o librerie condivise non entrano nell'immagine.
Il problema è, naturalmente, che le librerie dinamiche hanno il loro ciclo di vita che, in generale, potrebbe finire prima dell'applicazione principale dell'applicazione. Se un codice in una DLL registra una delle sue funzioni come callback atexit(), questa callback dovrebbe essere chiamata prima che la DLL venga scaricata. In caso contrario, si verificherà un arresto anomalo o qualcosa di peggio durante l'uscita principale dell'applicazione. (Per rendere le cose brutte crash durante l'uscita sono notoriamente difficili da eseguire il debug poiché molti debugger hanno problemi con i processi di morte).
Questo problema è molto più noto nel contesto dei distruttori di oggetti globali C++ (che, come detto sopra, sono i fratelli di atexit()). Ovviamente qualsiasi implementazione C++ su una piattaforma che supporta librerie dinamiche ha dovuto affrontare questo problema e la soluzione unanime era quella di chiamare i distruttori globali sia quando la libreria condivisa è scaricata o all'uscita dell'applicazione, a seconda di cosa si verifica per prima.
Fin qui tutto bene, tranne il fatto che alcune implementazioni "dimenticavano" di estendere lo stesso meccanismo alla semplice vecchia atexit(). Poiché lo standard C++ non dice nulla sulle librerie dinamiche tali implementazioni sono tecnicamente "corrette", ma questo non aiuta il programmatore povero che per un motivo o per un altro ha bisogno di chiamare atexit() passando una callback che risiede in una DLL.
Sulle piattaforme che conosco la situazione è la seguente. MSVC su Windows, GCC su Linux e Solaris e SunPro su Solaris hanno tutti un "giusto" atexit() che funziona allo stesso modo dei distruttori globali. Tuttavia, GCC su FreeBSD al momento della stesura di questo documento ha uno "rotto" che registra sempre i callback da eseguire sull'applicazione piuttosto che l'uscita della libreria condivisa. Tuttavia, come promesso, i distruttori globali funzionano bene anche su FreeBSD.
Cosa si deve fare nel codice portatile? Una soluzione è, ovviamente, evitare completamente atexit(). Se avete bisogno della sua funzionalità è facile sostituirlo con C++ distruttori nel seguente modo
//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();
...
Questo funziona a scapito di molta digitazione e l'interfaccia non intuitivo.È importante notare che non vi è alcuna perdita di funzionalità rispetto alla versione atexit(). Il distruttore di callback non può generare eccezioni, ma le funzioni invocate da atexit. La funzione callback :: register() potrebbe non essere thread-safe su una determinata piattaforma, ma è anche atexit() (lo standard C++ è attualmente silenzioso sui thread, quindi se implementare atexit() in modo thread-safe è fino all'implementazione)
E se si desidera evitare tutta la digitazione sopra? Di solito c'è un modo e si basa su un semplice trucco. Invece di chiamare broken atexit(), dobbiamo fare tutto ciò che il compilatore C++ fa per registrare i distruttori globali. Con GCC e altri compilatori che implementano il cosiddetto Itanium ABI (ampiamente utilizzato per piattaforme non Itanium) l'incantesimo magico si chiama __cxa_atexit. Ecco come usarlo. Prima inserire il codice di seguito in qualche utilità nell'intestazione
#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);
...
Il modo __cxa_atexit funziona come segue. Registra il callback in un singolo elenco globale allo stesso modo di atexit() non a DLL. Tuttavia associa anche gli altri due parametri. Il secondo parametro è solo una cosa bella da avere. Permette al callback di passare un certo contesto (come alcuni oggetti questo) e quindi un singolo callback può essere riutilizzato per più cleanup. Il terzo parametro è quello di cui abbiamo veramente bisogno. È semplicemente un "cookie" che identifica la libreria condivisa che dovrebbe essere associata al callback. Quando una libreria condivisa viene scaricata, il suo codice di pulizia attraversa l'elenco di callback atexit e chiama (e rimuove) tutte le richiamate che hanno un cookie che corrisponde a quello associato alla libreria che viene scaricata. Quale dovrebbe essere il valore del cookie? Non è l'indirizzo di avvio della DLL e non il suo handle dlopen() come si potrebbe supporre. Invece l'handle è memorizzato in una variabile globale speciale __dso_handle gestita dal runtime C++.
La funzione safe_atexit deve essere in linea. In questo modo seleziona qualsiasi __dso_handle utilizzato dal modulo di chiamata che è esattamente ciò di cui abbiamo bisogno.
Si dovrebbe utilizzare questo approccio al posto del più dettagliato e più portatile sopra? Probabilmente no, anche se chissà quali requisiti potresti avere. Tuttavia, anche se non lo usi mai, è utile essere consapevoli di come funzionano le cose, ecco perché è incluso qui.
Ciò implica che "dinamico" proviene da "libreria di caricamento dinamico"? – sharptooth
No, il termine si riferisce alla registrazione dinamica della richiamata della funzione atexit durante il runtime, che è in contrasto con la registrazione statica eseguita in fase di compilazione. è utile per le librerie di caricamento dinamiche poiché è possibile rimuovere una funzione di callback dall'elenco atexit se in qualche punto arbitrario la propria applicazione decide di scaricare una DLL precedentemente caricata, in tal caso è anche possibile chiamare manualmente qualsiasi codice di pulizia senza necessità di inoltrare su ateixt. Il collegamento – Alon
è morto. Dovresti considerare di copiare le informazioni pertinenti nella tua risposta. –