c++ - Clang links to different locations when referring a templated static variable from multiple compilation units -
in attempt compile existing (gcc developed) code base clang, we're facing interesting problem. result clang-compiled executable creates multiple instances of singletons. not sure if our usage , understanding in accordance standard, or if gcc an/or clang or c++ standard library , toolchain on linux have problem.
- we're using factory create singleton instances
- the actual creation delegated policy template
- at places, we're using variation of singleton factory, actual type of singleton can configured @ definition site, without necessity reveal concrete type client accessing singleton. client knows interface type
- the problem shows when referring "same" static variable through inlined function used different compilation units
the following excerpt, omitting locking, lifecycle issues, initialisation , clean-up
file-1: clang-static-init.hpp
#include <iostream> using std::cout; namespace test { /* === layer-1: singleton factory based on templated static variable === */ template<typename ///< interface of product type ,template <class> class fac ///< policy: actual factory create instance > struct holder { static i* instance; i& get() { if (!instance) { cout << "singleton factory: invoke fabrication ---> address of static instance variable: "<<&instance<<"...\n"; instance = fac<i>::create(); } return *instance; } }; /** * allocate storage per-type shared * (static) variable hold singleton instance */ template<typename ,template <class> class f > i* holder<i,f>::instance; template<typename c> struct factory { static c* create() { return new c(); } }; /* === layer-2: configurable product type === */ template<typename i> struct adapter { typedef i* factoryfunction (void); static factoryfunction* factoryfunction; template<typename c> static i* concretefactoryfunction() { return static_cast<i*> (factory<c>::create()); } template<typename x> struct adaptedconfigurablefactory { static x* create() { return (*factoryfunction)(); } }; }; /** storage per-type shared function pointer concrete factory */ template<typename i> typename adapter<i>::factoryfunction* adapter<i>::factoryfunction; template<typename c> struct typeinfo { }; /** * singleton factory ability configure actual product type c * @ \em definition site. users see interface type t */ template<typename t> struct configurableholder : holder<t, adapter<t>::template adaptedconfigurablefactory> { /** define actual product type */ template<typename c> configurableholder (typeinfo<c>) { adapter<t>::factoryfunction = &adapter<t>::template concretefactoryfunction<c>; } }; /* === actual usage: test case fabricating subject instances === */ struct subject { static int creationcount; subject(); }; typedef configurableholder<subject> accesspoint; /** singleton factory instance */ extern accesspoint fab; subject& fabricate(); } // namespace test
file-2: clang-static-init-1.cpp
#include "clang-static-init.hpp" test::subject& localfunction() { return test::fab.get(); } int main (int, char**) { cout << "\nstart testcase: invoking 2 instances of configurable singleton factory...\n\n"; test::subject& ref1 = test::fab.get(); test::subject& sub2 = test::fabricate(); ///note: invoking get() within compilation unit reveales problem test::subject& sub3 = localfunction(); cout << "sub1=" << &ref1 << "\nsub2="<< &sub2 << "\nsub3="<< &sub3 << "\n"; return 0; }
file-3: clang-static-init-2.cpp
#include "clang-static-init.hpp" namespace test { int subject::creationcount = 0; subject::subject() { ++creationcount; std::cout << "subject("<<creationcount<<")\n"; } namespace { typeinfo<subject> shall_build_a_subject_instance; } /** * instance of singleton factory * @note example we're using \em 1 * shared instance of factory. * yet still, 2 (inlined) calls get() function might * access different addresses embedded singleton instance */ accesspoint fab(shall_build_a_subject_instance); subject& fabricate() { return fab.get(); } } // namespace test
notable points
- we're using single instance of accesspoint
- yet still, different compilation units using (inlined) function
holder<t,f>::get()
, see different locations static variableinstance
- while actual ctor call
configurableholder
templated concrete type of singleton create, specific type info erased; should not bear relevance type ofadapter
orconfigurableholder
- if understanding correct, usages of
get()
should see same type ofholder
, same location of static variable embedded inholder
- but in fact clang compiled executable invokes factory again
sub2
, called aonther compilation unit, whilesub1
,sub3
share same singleton instance expected
interestingly, symbol table of executable built clang-3.0 shows static variable has been linked twice (the behaviour same when using clang-3.2)
10: 0000000000000000 0 file local default abs research/clang-static-init-1.cpp 11: 0000000000400cd0 11 func local default 14 global constructors keyed 12: 0000000000400b70 114 func local default 14 test::holder<test::subject, test::adapter<test::subject>::adaptedconfigurablefactory>::get() 13: 00000000004027e0 8 object local default 28 test::holder<test::subject, test::adapter<test::subject>::adaptedconfigurablefactory>::instance 14: 00000000004027d8 1 object local default 28 std::__ioinit 15: 0000000000400b10 62 func local default 14 __cxx_global_var_init 16: 0000000000000000 0 file local default abs research/clang-static-init-2.cpp 17: 00000000004010e8 0 notype local default 17 gcc_except_table9 18: 0000000000400e60 16 func local default 14 global constructors keyed 19: 00000000004027f9 1 object local default 28 test::(anonymous namespace)::shall_build_a_subject_instance 20: 0000000000400de0 114 func local default 14 test::holder<test::subject, test::adapter<test::subject>::adaptedconfigurablefactory>::get() 21: 0000000000402800 8 object local default 28 test::holder<test::subject, test::adapter<test::subject>::adaptedconfigurablefactory>::instance
...while relevant section of gcc-4.7.2 compiled executable reads expected
44: 0000000000400b8c 16 func global default 14 localfunction() 45: 00000000004026dc 1 object global default 28 test::fab 46: 0000000000400c96 86 func weak default 14 test::holder<test::subject, test::adapter<test::subject>::adaptedconfigurablefactory>::get() 47: 00000000004026e0 272 object global default 28 std::cout 48: 0000000000000000 0 func global default und std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(st 49: 0000000000400d4b 16 func global default 14 test::fabricate() 50: 0000000000000000 0 func global default und std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*) 51: 00000000004026d0 8 object unique default 28 test::holder<test::subject, test::adapter<test::subject>::adaptedconfigurablefactory>::instance 52: 0000000000400cec 15 func weak default 14 test::adapter<test::subject>::adaptedconfigurablefactory<test::subject>::create() 53: 00000000004026c8 8 object unique default 28 test::adapter<test::subject>::factoryfunction
we're using debian/stable 64bit (gcc-4.7 , clang-3.0) , debian/testing 32bit (clang-3.2) build
the fix declare singleton template class extern, , explicitly instantiate singleton in single compilation unit.
if compilation units in separate (shared) libraries, clang behaving way because can.
when code compiled, compiler instantiates singleton template every time specified. @ link time, 1 instantiation discarded. happens if have shared libraries in project, , there several link-times? each shared object have 1 instantiation of template. gcc ensures there 1 surviving template instantiation in final executable (maybe using vague linkage?), evidently clang not.
Comments
Post a Comment