从多个编译单元引用模板化静态变量时,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++标准库和工具链是否真的有问题。
我们正在使用工厂来创建单例实例 实际创建委托给策略模板 在某些地方,我们使用单例工厂的变体,其中可以在 定义 站点配置单例的实际类型,而无需向客户端访问单例。客户端只知道接口类型 通过不同编译单元使用的内联函数引用“相同”静态变量时出现问题以下是摘录,省略了任何锁定、生命周期问题、初始化和清理
文件-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
值得注意的点
我们只使用了一个接入点实例 然而,不同的编译单元使用(内联)函数Holder<T,F>::get()
,将看到静态变量instance
的不同位置
虽然对 ConfigurableHolder
的实际 ctor 调用是使用要创建的单例的具体类型模板化的,但此特定类型信息被删除;它不应与Adapter
或ConfigurableHolder
的类型有任何关联
如果这种理解是正确的,get()
的所有用法都应该看到相同类型的Holder
,因此嵌入在Holder
中的静态变量的位置相同
但实际上 Clang 编译的可执行文件再次为 sub2
调用工厂,这是从另一个编译单元调用的,而 sub1
和 sub3
与预期共享相同的单例实例
有趣的是,使用 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 项目时从静态库中隐藏符号