Еще как причем! Эта __cxa_atexit — не просто ценный мех, а еще внутренняя низкоуровневая функция рантайма C++. С ее помощью компилятор тихой сапой регистрирует деструкторы глобальных и статических объектов, чтобы они были корректно вызваны при завершении работы программы или при выгрузке динамической библиотеки.
Есть похожая функция в чистом C - atexit, но она не очень умеет с динамическими объектами работать, да и с деструкторами в общем-то не особо.
Вот, например, пишем такой глобальный объект
struct MyClass { ~MyClass() {}};
MyClass global_object;
Тогда GCC, никого не спрашивая, нагенерирует неявных вызовов под это дело
:
__cxa_atexit((void (*)(void *))&MyClass::~MyClass, &global_object, &__dso_handle);
В so-библиотеке этот деструктор сохраняется во внутренний список с соответствующим маркером __dso_handle.
Если мы вообще закрываем приложение, будет вызвана функция __cxa_finalize(&__dso_handle), а __dso_handle в этом случае нулевая. В таком случае Itanium C++ ABI велит вызвать последовательно все функции из расстрельного списка.
Гораздо веселее выгружать только so-шник. Это вызывает кратковременный dlclose, который в свою очередь дергает __cxa_finalize с __dso_handle, и тут уже она нулю не равна. Вот тогда мы вытаскиваем из списка все деструкторы с таким маркером и начинаем яростно, дико вызывать в порядке живой очереди.
Для сурового embedded я видел советы отключить этот механизм как избыточный, поскольку микроконтроллерам все равно не хочется жонглировать so-либами.
В GCC (и даже в clang) можно явно отключить этот механизм через флаг -fno-use-cxa-atexit.
В этом случае компилятор перестанет использовать __cxa_atexit, а указатели на деструкторы запишет в специальную секцию бинарного файла .fini_array (или .dtors), которые будут последовательно вызваны при выходе из программы самым простым циклом.
Впрочем, я не проверял. Должно работать.