2009-12-23 2 views
6

Недавно я портировал свое приложение с VC++ 7 на VC++ 9. Теперь он иногда сбой при выходе - среда выполнения запускает вызов деструкторов глобальных объектов, и в одном из них происходит нарушение прав доступа.Что означает «динамический» в «динамическом atexit destructor»?

Всякий раз, когда я наблюдатель вызов стека верхние функции являются:

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

Вопрос заключается в том, что смысл слова «динамический» в «динамическом atexit деструктора»? Может ли он предоставить любую дополнительную информацию мне?

ответ

7

его трудно определить точную проблему без фактического кода, но, возможно, вы можете найти его самостоятельно после прочтения этого:

из 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 используется вызывающим модулем, что именно то, что нам нужно.

Следует ли использовать этот подход вместо подробного и более переносного выше? Наверное, нет, хотя кто знает, какие у вас требования. Тем не менее, даже если вы никогда не используете его, это помогает осознавать, как все работает, поэтому именно здесь оно включено.

+0

Означает ли это, что «динамический» из «библиотеки динамической загрузки»? – sharptooth

+0

Этот термин не относится к динамической регистрации обратного вызова функции atexit во время выполнения, то есть в отличие от статической регистрации, выполненной во время компиляции. это полезно для библиотек динамической загрузки, так как вы можете удалить функцию обратного вызова из списка atexit, если в какой-то произвольной точке ваше приложение решит выгрузить ранее загруженную DLL, в этом случае вы также можете вручную вызвать любой код очистки без необходимости ретрансляции ateixt. – Alon

+0

ссылка мертва. Вы должны рассмотреть возможность копирования соответствующей информации в ваш ответ. –