Деструктор глобальной статической переменной в разделяемой библиотеке не вызывается на dlclose
В основной программе я dlopen
а также dlclose
(LoadLibrary
а также FreeLibrary
соответственно) общая библиотека. Общая библиотека содержит статическую переменную, которая создается при dlopen
и уничтожен на dlclose
, Это поведение согласуется с MSVC 2008 и 2013, GCC 3.4.6 и Sunstudio 12.1. Однако в GCC 4.9.1 и GCC 5.2.1 деструктор больше не вызывался dlclose
, Вместо этого он вызывался до выхода из программы.
Особенностью класса статической переменной является то, что в конструкторе есть вызов шаблонной функции get (глобальной области видимости), которая возвращает локальную статическую переменную.
Мне удалось воспроизвести это поведение с помощью следующего файла cpp, связанного с общей библиотекой:
#include <iostream>
template <typename T> // In my actual code, i is of type T, however, this has no effect
int get()
{
static int i = 0;
return i;
}
class Dictionary {
public:
Dictionary()
{
std::cout << "Calling Constructor" << std::endl;
get<int>();
}
~Dictionary(){
std::cout << "Calling Destructor" << std::endl;
}
private:
Dictionary(const Dictionary&);
Dictionary& operator=(const Dictionary&);
};
static Dictionary d;
Я исследовал настройки, которые могут быть сделаны для вызова деструктора dlclose, и пришел к выводу следующее:
- Если функция get не была шаблонной
- иначе, если переменная i в функции get не была статической
- иначе, если функция get сделана статической
Основной код программы следующий:
#include <dlfcn.h>
#include <cassert>
#include <string>
#include <iostream>
void* LoadLib(std::string name)
{
void* libInstance;
name = "lib" + name + ".so";
libInstance = dlopen(name.c_str(), RTLD_NOW);
if ( ! libInstance ) std::cout << "Loading of dictionary library failed. Reason: " << dlerror() << std::endl;
return libInstance;
}
bool UnloadLib(void* libInstance)
{
int ret = dlclose(libInstance);
if (ret == -1)
{
std::cout << "Unloading of dictionary library failed. Reason: " << dlerror() << std::endl;
return false;
}
return true;
}
int main()
{
void* instance = LoadLib("dll");
assert(instance != 0);
assert(UnloadLib(instance));
std::cout << "DLL unloaded" << std::endl;
}
Я собрал двоичные файлы с помощью следующих команд:
g++ -m64 -g -std=c++11 -shared -fPIC dll.cpp -o libdll.so
g++ -m64 -g -std=c++11 -ldl main.cpp -o main.out
Вывод, который я получаю при вызове деструктора до выхода из программы, следующий:
Calling Constructor
DLL unloaded
Calling Destructor
Вывод, который я получаю при вызове деструктора для dlclose, следующий:
Calling Constructor
Calling Destructor
DLL unloaded
Вопросы:
- Если изменение поведения между версиями GCC не является ошибкой, не могли бы вы объяснить, почему деструктор не вызывается в dlclose?
- Не могли бы вы объяснить для каждого изменения: почему в этом случае деструктор вызывается для dlclose?
1 ответ
Нет гарантии, что выгрузка (деструкторы вызываются) происходит на dlclose. В musl (в отличие от glibc) конструкторы запускаются только при первом запуске библиотеки, а деструкторы - при выходе. Для переносимого кода нельзя предполагать, что dlclose немедленно выгружает символы.
Поведение выгрузки зависит от привязки символов в glibc при динамическом связывании и не зависит от GCC.
Статическая переменная get::i
имеет STB_GNU_UNIQUE
связывание. Для статических переменных в встроенных функциях уникальность объекта обеспечивается компоновщиком ELF. Однако для динамической загрузки динамический компоновщик обеспечивает уникальность, отмечая символ STB_GNU_UNIQUE
, Следовательно, другая попытка отделить ту же разделяемую библиотеку некоторым другим кодом будет искать символ и обнаруживать, что он уникален, и возвращать существующую из таблицы уникальных символов. Символ с уникальной привязкой не может быть выгружен.
Уникальная привязка может быть отключена с помощью -fno-gnu-unique
если не нужно
Рекомендации