当从多个编译单元引用模板化静态变量时,Clang链接到不同的位置

时间:2013-10-06 18:38:41

标签: c++ gcc clang

在尝试用Clang编译现有的(GCC开发的)代码库时,我们面临着这个有趣的问题。因此, Clang编译的可执行文件会创建一些单例的多个实例。不确定我们的使用和理解是否符合标准,或者Linux上的GCC和/或Clang或C ++标准库和工具链是否确实存在问题。

  • 我们正在使用工厂创建单例实例
  • 将实际创建委派给策略模板
  • 在某些地方,我们使用的是单件工厂的变体,其中可以在定义站点配置单件的实际类型,而无需将该具体类型透露给客户端访问单身人士。客户端只知道接口类型
  • 通过不同编译单元使用的内联函数引用“相同”静态变量时出现问题

以下是摘录,省略任何锁定,生命周期问题,初始化和清理


文件-1 clang-static-init.hpp

#include <iostream>
using std::cout;

namespace test {

  /* === Layer-1: a singleton factory based on a templated static variable === */

  template<typename I                     ///< Interface of the product type
          ,template <class> class Fac     ///< Policy: actual factory to create the 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 for the per-type shared
   * (static) variable to hold the singleton instance
   */
  template<typename I
          ,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 for the per-type shared function pointer to the concrete factory */
  template<typename I>
  typename Adapter<I>::FactoryFunction*  Adapter<I>::factoryFunction;



  template<typename C>
  struct TypeInfo { };



  /**
   * Singleton factory with the ability to configure the actual product type C
   * only at the \em definition site. Users get to see only the interface type T
   */
  template<typename T>
  struct ConfigurableHolder
    : Holder<T, Adapter<T>::template AdaptedConfigurableFactory>
    {
      /** define the 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

文件-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 two instances of the configurable singleton factory...\n\n";

    test::Subject& ref1 = test::fab.get();
    test::Subject& sub2 = test::fabricate();  ///NOTE: invoking get() from within another compilation unit reveales the problem
    test::Subject& sub3 = localFunction();

    cout << "sub1="  << &ref1
         << "\nsub2="<< &sub2
         << "\nsub3="<< &sub3
         << "\n";


    return 0;
  }

文件-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 the singleton factory
   * @note especially for this example we're using just \em one
   *       shared instance of the factory.
   *       Yet still, two (inlined) calls to the get() function might
   *       access different addresses for the embedded singleton instance
   */
  AccessPoint fab(shall_build_a_Subject_instance);


  Subject&
  fabricate()
  {
    return fab.get();
  }


} // namespace test

值得注意的要点

  • 我们只使用AccessPoint的一个实例
  • 然而,使用(内联)函数Holder<T,F>::get()不同的编译单元会看到静态变量instance的不同位置
  • 虽然对ConfigurableHolder的实际ctor调用是使用要创建的单例的具体类型进行模板化的,但此特定类型信息已擦除;它与AdapterConfigurableHolder
  • 的类型不相关
  • 如果这种理解是正确的,get()的所有用法都应该看到相同类型的Holder,因此Holder
  • 中嵌入的静态变量的位置相同
  • 但事实上,Clang编译的可执行文件再次为sub2调用工厂,sub1从另一个编译单元调用,而sub310: 0000000000000000 0 FILE LOCAL DEFAULT ABS research/clang-static-init-1.cpp 11: 0000000000400cd0 11 FUNC LOCAL DEFAULT 14 global constructors keyed to a 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 to a 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 按预期共享相同的单例实例

有趣的是,使用Clang-3.0构建的可执行文件的符号表显示此静态变量已链接两次(使用Clang-3.2时行为相同)

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

...虽然GCC-4.7.2编译的可执行文件的相关部分按预期读取

{{1}}

我们正在使用Debian / stable 64bit(GCC-4.7和Clang-3.0)和Debian /测试32bit(Clang-3.2)来构建

1 个答案:

答案 0 :(得分:0)

修复是声明你的单例模板类extern,并在单个编译单元中显式实例化单例。

如果您的编译单元位于单独的(共享)库中,那么Clang的表现就是这样,因为它可以。

编译代码时,编译器会在每次完全指定时实例化单例模板。在链接时,除了一个实例之外的所有实例都被丢弃。但是,如果您的项目中有共享库,并且有几个链接时间会发生什么?每个共享对象都有一个模板实例。 GCC确保最终可执行文件中只有一个幸存的模板实例化(可能使用vague linkage?),但显然Clang没有。