В настоящее время я изучаю очень сложную ошибку в Boost.Serialization, связанную с синглетонами. Для контекста: Boost 1.65 изменил реализацию синглтона, нарушающего уведомление is_destructed
, что приводит к ошибкам при выходе из программы или выгрузке библиотеки. Boost 1.66 «исправил» это, но утечка памяти.
Синглтон-код (соответствующий этому вопросу) сводится к следующему:
template<class T> struct singleton{
T& inst(){
static T t;
return t;
}
}
Использование статической переменной-функции-члена позволяет избежать статического фиаско init init, но все равно имеет ту же проблему с уничтожением.
Однако Поиск проблем порядка статической инициализации C ++ показывает код, как решить эту проблему: когда Ctor A
использует B
, тогда B
будет создан первым и, следовательно, уничтожен последним. Это также указано в Порядок уничтожения статических объектов в C ++ . (completion of the destructor happens in the reverse order of the completion of the constructor
)
Пока все хорошо. Boost.Serialization теперь использует несколько синглетов типа extended_type_info_typeid<T>
для регистрации некоторых метаданных типа пользователя T
в другом синглтоне std::multiset<const bs::typeid_system::extended_type_info_typeid_0*,...>
. Это делается с помощью multiset
(предположим, что все синглеты отсюда) из конструктора extended_type_info_typeid_0
. В деструкторе extended_type_info_typeid_0
запись в multiset
удалена.
Это означает, что мы имеем именно ту ситуацию, которая описана выше, и multiset
должен пережить другие случаи.
Это ломается при использовании общих библиотек. У меня есть следующий тестовый пример:
test_multi_singleton.cpp:
int f();
int g();
int main(int argc, char**){
// Make sure symbols are used
if(argc==8) return f();
if(argc==9) return g();
}
multi_singleton1.cpp:
#include <boost/serialization/extended_type_info_typeid.hpp>
int f(){
return 0 != boost::serialization::extended_type_info_typeid<int>::get_const_instance().get_key();
}
multi_singleton2.cpp:
#include <boost/serialization/extended_type_info_typeid.hpp>
int g(){
// Use different(!) type
return 0 != boost::serialization::extended_type_info_typeid<float>::get_const_instance().get_key();
}
Build with:
g++ multi_singleton1.cpp -lboost_serialization -fPIC -shared -olibmulti_singleton1.so
g++ multi_singleton2.cpp -lboost_serialization -fPIC -shared -olibmulti_singleton2.so
g++ test_multi_singleton.cpp -L. -lmulti_singleton1 -lmulti_singleton2
Run in valgrind:
valgrind ./a.out
Видно, что это повреждает память в Boost 1.65. Причиной является испорченный порядок, который я отслеживал, угоняя и регистрируя вызовы ctor / dtor:
ctor 0x7f9f0aa074a0 std::multiset<const extended_type_info_typeid_0*>
ctor 0x7f9f0a7f63e0 extended_type_info_typeid<float>
ctor 0x7f9f0a7f64a0 std::multiset<const extended_type_info_typeid_0*>
ctor 0x7f9f0aa073e0 extended_type_info_typeid<int>
dtor 0x7f9f0aa073e0 extended_type_info_typeid<int>
dtor 0x7f9f0aa074a0 std::multiset<const extended_type_info_typeid_0*>
dtor 0x7f9f0a7f64a0 std::multiset<const extended_type_info_typeid_0*>
dtor 0x7f9f0a7f63e0 extended_type_info_typeid<float>
Это использует GCC 6.4, но то же самое с GCC 7.1. Как видите, 2 мультимножества уничтожаются вместе и до 2-го extended_type_info_typeid
.
Я что-то упустил? Это разрешено стандартом C ++?