Как правило нуля влияет на разделяемые библиотеки со скрытой видимостью?
ПРИМЕЧАНИЕ: вопрос находится внизу.
Я пытаюсь понять проблемы, которые могут возникнуть при использовании правила нуля с разделяемыми библиотеками и производными типами.
В демонстрации ниже компилируется с правилом нуля или без него, в зависимости от определения препроцессора. Затем сценарий демонстрирует возникающие различия, а именно то, что если
DerivedType
имеет ключевой метод, определенный в файле cpp, vtable генерируется только в разделяемой библиотеке, тогда как, если ключевого метода нет, vtable генерируется в каждом потребляющем TU.
baselib.h:
#ifndef BASELIB_H
#define BASELIB_H
#ifdef BUILD_BASE_LIB
#define BASELIB_EXPORTS __attribute__((visibility("default")))
#else
#define BASELIB_EXPORTS
#endif
#include <memory>
class BASELIB_EXPORTS BaseType
{
public:
BaseType() = default;
virtual ~BaseType();
BaseType(BaseType const&) = default;
BaseType(BaseType &&) = default;
BaseType& operator=(BaseType const&) = default;
BaseType& operator=(BaseType &&) = default;
};
class BASELIB_EXPORTS DerivedType : public BaseType
{
public:
#ifdef DERIVED_RULE_OF_FIVE
DerivedType() = default;
~DerivedType() override;
DerivedType(DerivedType const&) = default;
DerivedType(DerivedType &&) = default;
DerivedType& operator=(DerivedType const&) = default;
DerivedType& operator=(DerivedType &&) = default;
#endif
#ifdef DERIVED_TYPE_EXPLICIT_KEY
virtual void key();
#endif
};
BASELIB_EXPORTS std::unique_ptr<BaseType> makeBaseType();
#endif
baselib.cpp
#include "baselib.h"
#include <iostream>
BaseType::~BaseType() = default;
#ifdef DERIVED_RULE_OF_FIVE
DerivedType::~DerivedType() = default;
#endif
#ifdef DERIVED_TYPE_EXPLICIT_KEY
void DerivedType::key() {}
#endif
std::unique_ptr<BaseType> makeBaseType()
{
std::cout << "BASE LIB " << &typeid(DerivedType) << "\n";
return std::make_unique<DerivedType>();
}
otherlib.h:
#ifndef OTHERLIB_H
#define OTHERLIB_H
#ifdef BUILD_OTHER_LIB
#define OTHERLIB_EXPORTS __attribute__((visibility("default")))
#else
#define OTHERLIB_EXPORTS
#endif
#include "baselib.h"
class OTHERLIB_EXPORTS OtherDerivedType : public DerivedType
{
public:
OtherDerivedType() = default;
virtual ~OtherDerivedType();
OtherDerivedType(OtherDerivedType const&) = default;
OtherDerivedType(OtherDerivedType &&) = default;
OtherDerivedType& operator=(OtherDerivedType const&) = default;
OtherDerivedType& operator=(OtherDerivedType &&) = default;
std::unique_ptr<DerivedType> getDerivedType();
};
#endif
otherlib.cpp
#include "otherlib.h"
#include "baselib.h"
#include <iostream>
OtherDerivedType::~OtherDerivedType() = default;
std::unique_ptr<DerivedType> OtherDerivedType::getDerivedType()
{
std::cout << "OTHER LIB " << &typeid(DerivedType) << "\n";
auto bt = makeBaseType().release();
return std::unique_ptr<DerivedType>(dynamic_cast<DerivedType*>(bt));
}
main.cpp
#include "otherlib.h"
#include "baselib.h"
#include <iostream>
int main(int argc, char** argv)
{
std::cout << "MAIN " << &typeid(DerivedType) << "\n";
OtherDerivedType odt;
auto dt = odt.getDerivedType();
std::cout << "DT " << dt.get() << "\n";
return 0;
}
#!/bin/bash
$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_BASE_LIB -o baselib1.o -c baselib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o baselib1.so baselib1.o
$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_OTHER_LIB -o otherlib1.o -c otherlib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o otherlib1.so otherlib1.o baselib1.so
$COMPILER_DRIVER -o main1.o -c main.cpp
$COMPILER_DRIVER -o def_rule_zero_without_key main1.o otherlib1.so baselib1.so
$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_BASE_LIB -DDERIVED_TYPE_EXPLICIT_KEY -o baselib2.o -c baselib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o baselib2.so baselib2.o
$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_OTHER_LIB -DDERIVED_TYPE_EXPLICIT_KEY -o otherlib2.o -c otherlib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o otherlib2.so otherlib2.o baselib2.so
$COMPILER_DRIVER -o main2.o -c main.cpp
$COMPILER_DRIVER -o def_rule_zero_with_explicit_key main2.o otherlib2.so baselib2.so
$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_BASE_LIB -DDERIVED_RULE_OF_FIVE -DDERIVED_TYPE_EXPLICIT_KEY -o baselib3.o -c baselib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o baselib3.so baselib3.o
$COMPILER_DRIVER -fvisibility=hidden -fvisibility-inlines-hidden -fPIC -DBUILD_OTHER_LIB -DDERIVED_RULE_OF_FIVE -DDERIVED_TYPE_EXPLICIT_KEY -o otherlib3.o -c otherlib.cpp
$COMPILER_DRIVER -shared -Wl,--no-undefined -o otherlib3.so otherlib3.o baselib3.so
$COMPILER_DRIVER -o main3.o -c main.cpp
$COMPILER_DRIVER -o def_rule_five_explicit_key main3.o otherlib3.so baselib3.so
echo
echo "Runtime demonstration of difference:"
echo
echo "The typeid is different when using rule of zero without a key method"
LD_LIBRARY_PATH=. ./def_rule_zero_without_key
echo
echo "The typeid is the same with a non-dtor explicit key and a defaulted inline dtor"
LD_LIBRARY_PATH=. ./def_rule_zero_with_explicit_key
echo
echo "The typeid is the same when using rule of FIVE/SIX and an explicit key"
LD_LIBRARY_PATH=. ./def_rule_five_explicit_key
echo
echo "Static demonstration of difference (nm -o):"
echo
echo "DerivedType vtable is emitted in consumer when using rule of zero"
nm -o otherlib1.o | c++filt | grep vtable
echo
echo "DerivedType IS STILL visible (but externally defined) when using rule of zero with an explicit key"
nm -o otherlib2.o | c++filt | grep vtable
echo
echo "DerivedType not visible with an explicit dtor and another virtual"
nm -o otherlib3.o | c++filt | grep vtable
echo
echo "Static demonstration of difference (readelf -a):"
echo
echo "DerivedType vtable is emitted in consumer when using rule of zero"
readelf -a otherlib1.o | c++filt | grep vtable
echo
echo "DerivedType vtable still emitted when using a defaulted destructor and an explicit key, but is NOTYPE GLOBAL DEFAULT and UND instead of OBJECT WEAK HIDDEN"
readelf -a otherlib2.o | c++filt | grep vtable
echo
echo "DerivedType vtable is not emitted if the destructor is out of line"
readelf -a otherlib3.o | c++filt | grep vtable
echo
echo
echo "In otherlib1, the vtable is present but HIDDEN (Is this STV_HIDDEN?)"
вывод с g ++:
Runtime demonstration of difference:
The typeid is different when using rule of zero without a key method
MAIN 0x5580b2787d30
OTHER LIB 0x7f26b67f4dc8
BASE LIB 0x5580b2787d30
DT 0x559e26cc52c0
The typeid is the same with a non-dtor explicit key and a defaulted inline dtor
MAIN 0x55696d0f4d30
OTHER LIB 0x55696d0f4d30
BASE LIB 0x55696d0f4d30
DT 0x559e26cc52c0
The typeid is the same when using rule of FIVE/SIX and an explicit key
MAIN 0x5619fc118d30
OTHER LIB 0x5619fc118d30
BASE LIB 0x5619fc118d30
DT 0x559e26cc52c0
Static demonstration of difference (nm -o):
DerivedType vtable is emitted in consumer when using rule of zero
otherlib1.o:0000000000000000 V vtable for DerivedType
otherlib1.o:0000000000000000 V vtable for OtherDerivedType
otherlib1.o: U vtable for __cxxabiv1::__si_class_type_info
DerivedType IS STILL visible (but externally defined) when using rule of zero with an explicit key
otherlib2.o: U vtable for DerivedType
otherlib2.o:0000000000000000 V vtable for OtherDerivedType
otherlib2.o: U vtable for __cxxabiv1::__si_class_type_info
DerivedType not visible with an explicit dtor and another virtual
otherlib3.o:0000000000000000 V vtable for OtherDerivedType
otherlib3.o: U vtable for __cxxabiv1::__si_class_type_info
Static demonstration of difference (readelf -a):
DerivedType vtable is emitted in consumer when using rule of zero
COMDAT group section [ 35] `.group' [vtable for OtherDerivedType] contains 2 sections:
COMDAT group section [ 36] `.group' [vtable for DerivedType] contains 2 sections:
000000000013 00770000002a R_X86_64_REX_GOTP 0000000000000000 vtable for OtherDerivedType - 4
000000000013 007000000002 R_X86_64_PC32 0000000000000000 vtable for DerivedType + c
112: 0000000000000000 32 OBJECT WEAK HIDDEN 112 vtable for DerivedType
119: 0000000000000000 32 OBJECT WEAK DEFAULT 110 vtable for OtherDerivedType
DerivedType vtable still emitted when using a defaulted destructor and an explicit key, but is NOTYPE GLOBAL DEFAULT and UND instead of OBJECT WEAK HIDDEN
COMDAT group section [ 35] `.group' [vtable for OtherDerivedType] contains 2 sections:
000000000013 00710000002a R_X86_64_REX_GOTP 0000000000000000 vtable for OtherDerivedType - 4
000000000013 006b0000002a R_X86_64_REX_GOTP 0000000000000000 vtable for DerivedType - 4
107: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND vtable for DerivedType
113: 0000000000000000 40 OBJECT WEAK DEFAULT 107 vtable for OtherDerivedType
DerivedType vtable is not emitted if the destructor is out of line
COMDAT group section [ 34] `.group' [vtable for OtherDerivedType] contains 2 sections:
000000000013 00670000002a R_X86_64_REX_GOTP 0000000000000000 vtable for OtherDerivedType - 4
103: 0000000000000000 40 OBJECT WEAK DEFAULT 102 vtable for OtherDerivedType
In otherlib1, the vtable is present but HIDDEN (Is this STV_HIDDEN?)
ПРИМЕЧАНИЕ: ВОПРОС:
Есть ли какой-то режим, с помощью которого
dynamic_cast
не удастся или какой-либо другой режим сбоя, который может быть трудно отладить, если vtables генерируются в нескольких TU и общих библиотеках?