In C++, two types that have the same (fully qualified) name must be the same. The One Definition Rule (ODR) is there to ensure that, even if the types appear in different compilation units. C++ implementations are not required to diagnose all ODR violations, but they could be observed when features relying on runtime type information (exception handling,
-fsanitize=vptr, …) start to misbehave.
What the C++ standard does not cover is how to pick unique names (by picking unique namespaces). That prevents composability. If you have two C++ libraries whose source code you cannot change, you cannot in general link them into the same program, as that might cause ODR violations.
What the C++ standard does not cover either is dynamic libraries and symbol visibility across such libraries. That lead C++ implementations to slightly bend the ODR rules in order to mitigate the above problem: If two different types that happen to have the same fully qualified name are hidden in two different dyanmic libraries, that is technically still an ODR violation, but no problems can arise from it. (In theory, it might also be possible for each library to place all its “internal” entities into unnamed namespaces, which would solve the same problems. In practice, however, a library’s developers will most likely want to spread it across multiple compilation units and still use such internal entities across compilation units, where unnamed namespaces would no longer work.)
A key aspect of the above is how to hide a type in a dynamic library. RTTI for a type is typically represented in C++ implementations as a set of symbols, one of them denoting a C-style string representation of the (mangled) type name. Then, if a C++ implementation uses comparison of string addresses (and not of string contents) to determine type equality, and if the RTTI string symbols are not coalesced across dynamic libraries at runtime (e.g., by keeping them non-exported), then hiding works.
And, on the other hand, an important corollary of the above is that if uses of a type across multiple dynamic objects shall be considered equal (so that e.g. an instance of that type can be thrown in one dynamic library and caught in another), the corresponding RTTI string symbols do need to be coalesced at runtime (e.g., by exporting them as weak symbols from all the dynamic objects using them).
That is why the Itanium C++ ABI mandates address comparison of RTTI strings. It is not only faster (to do the comparison, at least; though not necessarily to load the dynamic libraries and resolve the weak symbols for those types that shall be considered equal across dynamic libraries), it also enables composability.
However, a third thing the C++ standard does not cover is dynamic loading of (dynamic) libraries, with mechanisms like POSIX
dlopen. An invocation of
dlopen can be either
RTLD_GLOBAL makes the exported symbols of the loaded library available to subsequently loaded libraries (which is important if there is any types that shall be considered equal across those libraries, see above),
RTLD_LOCAL does not—and can thus break things like throwing exceptions across libraries.
RTLD_LOCAL problem caused GCC maintainers to deviate from the Itanium C++ ABI and compare RTTI string symbols by content rather than by address. That implies breaking composability.
Clang still sticks to the Itanium C++ ABI’s by-address comparison, but that difference between GCC and Clang does not normally make a difference on Linux: Things like exception handling and
dynamic_cast are handled by the C++ runtime library, and on Linux that is GCC’s
libstdc++ regardless whether you compile wih GCC or Clang.
One case where it does make a difference is
-fsanitize=vptr, UBSan checks detecting certain undefined behavior involving function pointers or polymorphic object types, respectively. For Clang, the RTTI comparisons internally done by those checks are hard-coded in the compiler and would not call into
libstdc++. So, when using
- For cases where types shall be considered equal across dynamic objects, but GCC/
libstdc++would get away with hiding the RTTI string symbols due to the by-content comparison scheme, Clang on Linux needs special provisions, see “For Clang
- For cases where types shall be considered equal across
dlopen‘ed libraries, and GCC/
libstdc++would get away with using
RTLD_LOCALdue to the by-content comparison scheme, Clang on Linux needs to use
RTLD_GLOBALinstead, see “Ensure RTTI symbol visibility for Linux Clang