его трудно определить точную проблему без фактического кода, но, возможно, вы можете найти его самостоятельно после прочтения этого:
из http://www.gershnik.com/tips/cpp.asp (ссылка теперь мертв см ниже)
atexit() и динамические/разделяемые библиотеки
Стандартные библиотеки C и C++ включают иногда полезную функцию: atexit(). Он позволяет вызывающему абоненту регистрировать обратный вызов, который будет вызываться, когда приложение выходит (обычно). В C++ он также интегрирован с механизмом, который вызывает деструкторы глобальных объектов, поэтому вещи, созданные до того, как данный вызов atexit() будут уничтожены до обратного вызова и наоборот. Все это должно быть хорошо известно, и оно отлично работает, пока в изображение не попадут библиотеки DLL или разделяемые библиотеки.
Проблема, конечно, в том, что динамические библиотеки имеют собственное время жизни, которое в общем случае может закончиться до основного приложения приложения. Если код в DLL регистрирует одну из своих функций как обратный вызов atexit(), этот обратный вызов следует лучше вызвать до того, как DLL будет выгружена. В противном случае во время выхода основного приложения произойдет сбой или что-то еще хуже. (Чтобы сделать неприятные сбои во время выхода, как правило, трудно отлаживать, так как многие отладчики имеют проблемы с процессами умирания).
Эта проблема намного лучше известна в контексте деструкторов глобальных объектов C++ (которые, как упоминалось выше, являются братьями atexit()). Очевидно, что любая реализация на C++ на платформе, поддерживающей динамические библиотеки, должна была решить эту проблему, и единодушное решение заключалось в вызове глобальных деструкторов либо при разгрузке разделяемой библиотеки, либо при выходе из приложения, в зависимости от того, что наступит раньше.
Пока все хорошо, за исключением того, что некоторые реализации «забыли» расширить тот же механизм до простого старого atexit(). Поскольку стандарт C++ ничего не говорит о динамических библиотеках, такие реализации технически «правильны», но это не помогает бедному программисту, который по той или иной причине должен вызывать atexit() передачу обратного вызова, который находится в DLL.
На платформах я знаю, что ситуация такова. MSVC на Windows, GCC на Linux и Solaris и SunPro на Solaris имеют «правильный» atexit(), который работает так же, как и глобальные деструкторы. Однако GCC на FreeBSD на момент написания этой статьи имеет «сломанный», который всегда регистрирует обратные вызовы, которые должны выполняться в приложении, а не выход из общей библиотеки. Однако, как и было обещано, глобальные деструкторы отлично работают даже на 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() в потокобезопасном режиме до реализации)
Что делать, если вы хотите избежать ввода текста выше? Обычно есть способ, и он опирается на простой трюк. Вместо вызова сломанного atexit() нам нужно делать все, что делает компилятор C++ для регистрации глобальных деструкторов. С 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(). Однако он также связывает с ним другие два параметра. Второй параметр - это просто приятно иметь вещь. Он позволяет передать обратный вызов в какой-то контекст (например, некоторый объект), и поэтому один обратный вызов может быть повторно использован для нескольких очисток. Третий параметр - тот, который нам действительно нужен. Это просто «cookie», который идентифицирует общую библиотеку, которая должна быть связана с обратным вызовом. Когда какая-либо разделяемая библиотека выгружается, ее код очистки перемещается в список обратного вызова atexit и вызывает (и удаляет) любые обратные вызовы, которые имеют файл cookie, который соответствует тому, который связан с выгружаемой библиотекой. Какая должна быть ценность печенья? Это не адрес запуска DLL, а не его дескриптор dlopen(), как можно было бы предположить. Вместо этого дескриптор хранится в специальной глобальной переменной __dso_handle, поддерживаемой средой выполнения C++.
Функция safe_atexit должна быть встроенной. Таким образом, он выбирает все, что __dso_handle используется вызывающим модулем, что именно то, что нам нужно.
Следует ли использовать этот подход вместо подробного и более переносного выше? Наверное, нет, хотя кто знает, какие у вас требования. Тем не менее, даже если вы никогда не используете его, это помогает осознавать, как все работает, поэтому именно здесь оно включено.
Означает ли это, что «динамический» из «библиотеки динамической загрузки»? – sharptooth
Этот термин не относится к динамической регистрации обратного вызова функции atexit во время выполнения, то есть в отличие от статической регистрации, выполненной во время компиляции. это полезно для библиотек динамической загрузки, так как вы можете удалить функцию обратного вызова из списка atexit, если в какой-то произвольной точке ваше приложение решит выгрузить ранее загруженную DLL, в этом случае вы также можете вручную вызвать любой код очистки без необходимости ретрансляции ateixt. – Alon
ссылка мертва. Вы должны рассмотреть возможность копирования соответствующей информации в ваш ответ. –