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

Posted

技术标签:

【中文标题】从多个编译单元引用模板化静态变量时,Clang 链接到不同位置【英文标题】:Clang links to different locations when referring a templated static variable from multiple compilation units 【发布时间】:2013-10-06 18:38:41 【问题描述】:

在尝试使用 Clang 编译现有的(由 GCC 开发的)代码库时,我们遇到了这个有趣的问题。结果,Clang 编译的可执行文件创建了一些单例的多个实例。不知道我们的使用和理解是否符合标准,或者Linux上的GCC和/或Clang或C++标准库和工具链是否真的有问题。

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

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


文件-1clang-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

文件 2clang-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;
  

文件 3clang-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

值得注意的点

我们只使用了一个接入点实例 然而,不同的编译单元使用(内联)函数Holder&lt;T,F&gt;::get(),将看到静态变量instance的不同位置 虽然对 ConfigurableHolder 的实际 ctor 调用是使用要创建的单例的具体类型模板化的,但此特定类型信息被删除;它不应与AdapterConfigurableHolder 的类型有任何关联 如果这种理解是正确的,get() 的所有用法都应该看到相同类型的Holder,因此嵌入在Holder 中的静态变量的位置相同 但实际上 Clang 编译的可执行文件再次为 sub2 调用工厂,这是从另一个编译单元调用的,而 sub1sub3 与预期共享相同的单例实例

有趣的是,使用 Clang-3.0 构建的可执行文件的符号表显示此静态变量已链接两次(使用 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 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

...而 GCC-4.7.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

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

【问题讨论】:

您是否设法解决了这个问题?我在使用内联函数(未模板化)中的简单静态实例时遇到了类似的问题。似乎创建了多个实例(我不确定是每个编译单元还是线程)。一旦我将功能从内联转为独立,问题就解决了。 从 clang 切换到 gcc 4.6 时,问题也得到了解决。 恕我直言,这个问题看起来像是一个非常隐蔽的编译器错误。但我还没有验证它在最近的 CLang 版本中是否存在。应该可能会打开一个错误报告。 无论如何,我在我们的项目中通过重构整个情况来解决它:) -- 你是对的:只有 Clang 受到影响。我们测试的所有 GCC 版本都可以。 【参考方案1】:

解决方法是声明您的单例模板类 extern,并在单个编译单元中显式实例化单例。

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

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

【讨论】:

在处理共享库时同意。但在这种情况下,我们将一个普通的静态链接添加到一个可执行文件中。 Clang 在标准情况下会正确处理此问题,但无法在此处检测和删除这种情况下的虚假实例,它们通过类型擦除变为相同类型。我看不出为什么 Clang 链接器应该能够以正确的方式捕捉和解决这种情况。

以上是关于从多个编译单元引用模板化静态变量时,Clang 链接到不同位置的主要内容,如果未能解决你的问题,请参考以下文章

使用 Clang 作为编译器构建 Android 项目时从静态库中隐藏符号

MSVC 导致静态 const 模板成员初始化失败

没有前向声明的嵌套函数模板实例化可以在 GCC 上编译,但不能在 clang 上编译

哪些值可以分配给 `constexpr` 引用?

静态模板成员变量具有内部链接但未定义

C ++为将(静态)const全局变量设置为另一个静态const变量而给出编译器错误