Non-colliding Environment friendly type_info::hash_code Throughout Shared Libraries

[ad_1]


C++ customary library has std::type_info and std::type_index to get run-time kind details about a sort. There are some effectivity and robustness points in utilizing them (particularly when dynamically loaded libraries are concerned.)

TL;DR; The -D__GXX_MERGED_TYPEINFO_NAMES -rdynamic compiler/linker choices (for each the principle program and the library) generates code that makes use of pointer comparability in std::type_info::operator==().

The typeid key phrase is used to acquire a sort’s run-time kind data. Quoting cppreference.

The typeid expression is an lvalue expression which refers to an object with static storage period, of the polymorphic kind const std::type_info or of some kind derived from it.

std::type_info objects cannot be put in std::vector as a result of they’re non-copyable and non-assignable. In fact, you may have a std::vector<const std::type_info *> as the article returned by typeid has static storage period. You may additionally use std::vector<std::type_index>. std::type_index comprises a pointer to std::type_info and due to this fact, copies are doable and low cost. It is also safer to make use of std::type_index as a result of for associative containers, std::type_index delegates less-then, equality, and greater-than to the underlying std::type_info object. And that is what you need. Simply utilizing const std::type_info * would do pointer comparisons. The consequence could also be totally different.

The actual query I am searching for a solution to is

Is C++ typeid equipment dependable for figuring out uniqueness of polymorphic sorts robustly, effectively, portably, and throughout dynamically loaded modules?

This looks like a tall order. There’s one caveat although. “Portability” for me is restricted to RHEL7 Linux, MacOS 10.x, and could also be Home windows 10 with actually newest toolchains (clang++ 7.x, g++ 8.x, Visible Studio 2017). I am not nervous about different platforms in the meanwhile.

Robustness

Step one is to test if std::type_info or std::type_index is identical for a similar kind and never identical for various sorts.

We have a number of issues to make use of for comparisons:

  • std::type_info::operator==()
  • std::type_info::title()
  • std::type_info::hash_code()
  • std::type_info *

Contemplate type_info::operator==. Equality comparability between two type_info objects returns true for a similar sorts and false for various sorts even when dynamically loaded libraries are concerned. The query is how briskly is it. We’ll have a look at that slightly later.

The worst perform for figuring out equality seems to be type_info::title. Quoting cppreference: “No ensures are given; particularly, the returned string might be an identical for a number of sorts”. I am actually bummed by that.

Subsequent is type_info::hash_code. As hashes for 2 differing types can collide, it’s ineffective for figuring out kind equality. The one factor C++17 customary (n4713) says is

Inside a single execution of this system, it shall return the identical worth for any two type_info objects which evaluate equal.

Hash calculation may be sluggish as it might be sometimes O(n) the place n is the size of the mangled title. There’s one implementation-specific hack although. Sure preprocessor macros (mentioned under) allow type_info::hash_code to return a pointer to type_info object. That is super-fast. However does it present ensures of uniqueness? Could also be so.

That brings us to the final possibility: std::type_info *. If std::type_info::operator==() is applied by way of pointer comparisons, then we would get the most effective of each worlds. Quick, dependable type_info comparisons. Is there a means? Learn on…

Nevertheless, when shared libraries (.so on Linux, .dll on Home windows) are within the image, no such assure might be given. And it is smart. As shared-library and the principle program may very well be compiled utterly independently, anticipating that typeid(Foo) is identical object in most important and dynamically loaded libraries is wishful pondering. We’ll sort out this subject after the subsequent part.

Effectivity

For those who have a look at std::type_info in libc++ and libstdc++ you’ll uncover a few macros that straight decide effectivity of the comparability operators. It is _LIBCPP_HAS_NONUNIQUE_TYPEINFO in libc++ and __GXX_MERGED_TYPEINFO_NAMES in libstdc++ respectively. Within the respective library implementations, they management whether or not std::type_info comparisons are merely pointer comparisons or rather more costly const char * comparisons. With lengthy names of template instantiations, the price of strcmp-like operations may very well be excessive.

In case you are excited about detailed efficiency numbers and library code, it’s possible you’ll need to checkout Enjoyable with typeid() blogpost by David Holmes. The lengthy and the in need of it’s that with _LIBCPP_HAS_NONUNIQUE_TYPEINFO disabled in libc++ and __GXX_MERGED_TYPEINFO_NAMES enabled in libstdc++, efficiency of std::type_info and std::type_index comparisons is an order of magnitude higher (as a consequence of simply pointer comparisons).

On my MacOS machine, _LIBCPP_HAS_NONUNIQUE_TYPEINFO isn’t outlined by default. So issues are good. On my RHEL7 field, __GXX_MERGED_TYPEINFO_NAMES isn’t outlined. There’s reason that is the case in libstdc++. It reads one thing like this.

// Decide whether or not typeinfo names for a similar kind are merged (during which
// case comparability can simply evaluate pointers) or not (during which case strings
// should be in contrast), and whether or not comparability is to be applied inline or
// not.  

// We used to do inline pointer comparability by default if weak symbols
// can be found, however even with weak symbols generally names aren't merged
// when objects are loaded with RTLD_LOCAL, so now we all the time use strcmp by
// default.  

// For ABI compatibility, we do the strcmp inline if weak symbols
// can be found, and out-of-line if not.  Out-of-line pointer comparability
// is used the place the article recordsdata are to be moveable to a number of techniques,
// a few of which can not be capable to use pointer comparability, however the
// explicit system for which libstdc++ is being constructed can use pointer
// comparability; particularly for many ARM EABI techniques, the place the ABI
// specifies out-of-line comparability.  

// The compiler's goal configuration
// can override the defaults by defining __GXX_TYPEINFO_EQUALITY_INLINE to
// 1 or 0 to point whether or not or not comparability is inline, and
// __GXX_MERGED_TYPEINFO_NAMES to 1 or 0 to point whether or not or not pointer
// comparability can be utilized.

Thats’ dense! I am unclear about what merged actually means on this context. What’s being merged with what? Anybody?

The most effective half is the final sentence. The usual library authors are allowing setting an in any other case inner macro (begins with __) to allow pointer comparisons. So there appears to be gentle on the finish of the tunnel.

One factor I am not 100% positive is the key phrase “goal configuration”. A compiler’s goal configuration is the machine meeting code is generated for. On my machine, gcc -v prints Goal: x86_64-redhat-linux. I.e., the ensuing code is appropriate for operating on x86_64-redhat-linux—a native construct. I am unclear whether or not the compiler and the usual library itself needs to be constructed with the identical preprocessor macro. In case you are interested in what construct, host, and goal machines are for a compiler, see gcc configure phrases and historical past.

The next invocation of the compiler appears to provide code that makes use of pointer comparisons in type_info::operator==.

g++ -std=c++11 -D__GXX_MERGED_TYPEINFO_NAMES -ldl -o take a look at take a look at.cpp

Dynamically Loaded Libraries

There’s one other wrinkle which seems to be round dynamic loading of shared libraries. One thing about “weak symbols” and RTLD_LOCAL. What on this planet are these issues?

Within the man pages for dlopen—a library perform to load shared library recordsdata (*.so) at run-time—you will discover RTLD_LOCAL. Quoting man pages:

That is the converse of RTLD_GLOBAL, and the default if neither flag is specified. Symbols outlined on this library aren’t made accessible to resolve references in subsequently loaded libraries.

So in case your program makes use of dynamically loaded libraries and the libraries depend on a globally recognized definition of std::type_info(Foo) object, you is perhaps out of luck if the libraries are opened utilizing default flags or explicitly with RTLD_LOCAL. Such libraries, even when compiled with __GXX_TYPEINFO_EQUALITY_INLINE, will use their very own native definitions of std::type_info(Foo). Clearly, in case your program depends on a worldwide distinctive definition, as in std::set<std::type_index> or some related shenanigans, your program is prone to explode.

Okay, so, I am unable to open the libraries with RTLD_LOCAL or default. I’ve to make use of RTLD_GLOBAL. Simple.

To be further cautious, I threw in a run-time test to make sure the principle program and the shared-library file agree on the definition of std::type_info of Foo.

The Foo header file.

// Foo.h
#ifndef FOO_H
#outline FOO_H

namespace take a look at {
class Foo {
  digital ~Foo() = default;
};
}
utilizing namespace take a look at;
extern "C" void foo(const std::type_info &);
 
#endif  // FOO_H

The Foo implementation file.

// Foo.cpp (shared-library implementation)
#embrace <iostream>
#embrace <typeinfo> 
#embrace <cassert>

#embrace "foo.h"

void take a look at(const std::type_info &different)
{
  assert(different == typeid(Foo));
  std::cout << "typeid equality = " << std::boolalpha << (different == typeid(Foo)) << std::endl;
  assert(different.hash_code() == typeid(Foo).hash_code());
  std::cout << "typeid hash_code equality = " << std::boolalpha << (different.hash_code() == typeid(Foo).hash_code()) << std::endl;
  std::cout << "typeid title: module=" << typeid(Foo).title() << ", different=" << different.title() << std::endl;
}

And the principle program (robust_typeid.cpp)

#embrace <typeinfo>
#embrace <iostream>
#embrace <string>
#embrace <unistd.h>
#embrace <dlfcn.h>

#embrace "foo.h"

int most important(void) {
  char cwd[1024];
  getcwd(cwd, sizeof(cwd));
  std::string path = std::string(cwd) + "/libfoo.so";
  void *deal with = dlopen(path.c_str(), RTLD_GLOBAL);

  std::cout << "deal with = " << deal with << "n";
  utilizing TestFunctionType = void (*)(const std::type_info &); 
  TestFunctionType foo_ptr = reinterpret_cast<TestFunctionType>(dlsym(deal with, "take a look at"));

  if(test_ptr) 
    test_ptr(typeid(Foo));
  
  if(deal with)
    dlclose(deal with);
}

This system masses libfoo.so dynamically and calls the take a look at perform within the library. The primary module passes a reference to Foo‘s std::type_info object (as noticed by the principle module) to perform take a look at. The perform checks in the event that they agree on the distinctiveness of std::type_info object for Foo.

Lastly, the compiler choices.

// Create libfoo.so
$ clang++ -std=c++11 -D__GXX_MERGED_TYPEINFO_NAMES -fpic -shared foo.cpp -o libfoo.so
// Create the principle program
$ clang++ -std=c++11 -D__GXX_MERGED_TYPEINFO_NAMES -ldl -o robust_typeid robust_typeid.cpp
// Run
$ /.robust_typeid

It crashes with an assertion failure. Ouch!

deal with = 0x85dcf0
robust_typeid: foo.cpp:9: void take a look at(const std::type_info &): Assertion different == typeid(Foo) failed.
Aborted (core dumped)

Suspicion turned to be proper. One thing’s not proper.

With some google-foo, I discovered gcc’s linker flag -rdynamic or -export-dynamic. Quoting man pages:

This instructs the linker so as to add all symbols, not solely used ones, to the dynamic image desk. This feature is required for some makes use of of dlopen

Let’s strive.

Voilla!

These two choices appear to allow the most effective of each worlds: quick, dependable type_info comparisons. Moreover, the type_info::hash_code perform returns a pointer. Does that make it non-colliding? Is -D__GXX_MERGED_TYPEINFO_NAMES -rdynamic actually a silver bullet? Let me know what you suppose. Touch upon reddit/r/cpp.

[ad_2]

Leave a Reply

Your email address will not be published. Required fields are marked *