Деструктор глобальной статической переменной в разделяемой библиотеке не вызывается на 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 если не нужно

Рекомендации

Ошибка, которую я поднял в GCC

STB_GNU_UNIQUE

Другие вопросы по тегам