2013-12-15 86 views
10

gcc是否对静态成员初始化时间有任何保证,特别是关于模板类?gcc中模板的非延迟静态成员初始化?

我想知道我是否能够很好地保证静态成员(PWrap_T<T>::p_s)将在main()之前初始化,当时类在多个编译单元中实例化。在main的开始处尝试手动触摸每个编译单元中的符号是不现实的,但我不清楚其他任何方法都可行。

我已经喜欢在不同的单位bar()总是得到期望的结果的方法测试,但我需要知道当/如果曾经GCC将抽出地毯出来,无论是可以预防的。

此外,在库加载完成之前,DSO中的所有静态成员都会被初始化吗?

#include <iostream> 
#include <deque> 

struct P; 
inline std::deque<P *> &ps() { static std::deque<P *> d; return d; } 
void dump(); 

struct P { 
    P(int id, char const *i) : id_(id), inf_(i) { ps().push_back(this); } 
    void doStuff() { std::cout << id_ << " (" << inf_ << ")" << std::endl; } 
    int const  id_; 
    char const *const inf_; 
}; 

template <class T> 
struct PWrap_T { static P p_s; }; 

// *** Can I guarantee this is done before main()? *** 
template <class T> 
P PWrap_T<T>::p_s(T::id(), T::desc()); 

#define PP(ID, DESC, NAME) /* semicolon must follow! */ \ 
struct ppdef_##NAME {         \ 
    constexpr static int   id() { return ID; }  \ 
    constexpr static char const *desc() { return DESC; } \ 
};              \ 
PWrap_T<ppdef_##NAME> const NAME 

// In a compilation unit apart from the template/macro header. 
void dump() { 
    std::cout << "["; 
    for (P *pp : ps()) { std::cout << " " << pp->id_ << ":" << pp->inf_; } 
    std::cout << " ]" << std::endl; 
} 

// In some compilation unit. 
void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s.doStuff(); 
    } 
} 

int main() { 
    dump(); 
    PP(3, "another", pp2); 
    bar(5); 
    pp2.p_s.doStuff(); 
} 

(C++ 11§3.6.2/ 4 - [basic.start.init] :)

它实现定义是否一个非本地的动态初始化具有静态存储持续时间的变量在main的第一条语句之前完成。 如果初始化被推迟到main的第一条语句之后的某个时间点,它应该在第一个odr-use(3.2)之前出现,在任何函数或变量中定义在同一个翻译单元中作为要初始化的变量。

...具有初始化带副作用的静态存储持续时间的非局部变量必须初始化,即使它没有使用odr(3.2,3.7.1)。

此外,试图__attribute__ ((init_priority(int)))__attribute__ ((constructor))为模板成员的初始化产生warning: attributes after parenthesized initializer ignored,我知道关于静态初始化没有其他招数。

在此先感谢任何能够给我答案的人!

+0

我想象中的'ODR-use'规则意在包括可能有文件范围的对象的动态共享对象(的DSO)。如果DSO在主启动后被带入'dlopen()',那么显然不能初始化DSO中的所有内容,但理论上'dlopen()'可以在调用DSO中的其他任何内容之前确保DSO中的所有内容都已初始化。我想最终的答案是由ABI为您编译的任何操作系统/体系结构定义的。 –

+0

单身模式解决了这个问题,不是吗? – lkanab

回答

2

该标准保证静态存储持续时间对象在与外部源一起使用与您的对象相同的翻译单元中的任何功能/变量之前被初始化。

这里的措辞旨在与共享库协同工作。由于共享库可以在main()启动后动态加载,因此语言规范必须足够灵活以应对它。但只要你从翻译单位之外访问你的对象,那么你就可以保证它在你被授予访问权限之前就已经被构建了(除非你正在做一些病态的事情)。

但是如果它在同一编译单元中的另一个静态存储持续时间对象的构造函数中使用,则不会停止在初始化之前使用它。

但是,您可以通过使用下面的技术轻松手动提供保证,以便在使用之前正确初始化静态对象。

只需将静态变量更改为静态函数即可。然后在函数内部声明一个返回的静态成员。所以你可以像以前一样使用它(只需添加())。

template <class T> 
struct PWrap_T 
{ 
    static P& p_s(); // change static variable to static member function. 
         // Rather than the type being P make it a reference to P 
         // because the object will be held internally to the function 
}; 

template <class T> 
P& PWrap_T<T>::p_s() 
{ 
    // Notice the member is static. 
    // This means it will live longer than the function. 
    // Also it will be initialized on first use. 
    // and destructed when other static storage duration objects are destroyed. 
    static P p_s_item(T::id(), T::desc()); 

    return p_s_item; 

    // Note its not guaranteed to be created before main(). 
    // But it is guaranteed to be created before first use. 
} 

所以在这里你可以得到一个全球性的好处(无论它们是什么)。你可以得到有保证的施工/销毁,并且你知道物体在被使用之前会被正确地构造。

你需要做的唯一的变化是:

void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s().doStuff(); 
    // ^^ Add the braces here. 
    } 
} 
+0

唯一的问题是,在pre-C++ 11 iirc中,函数静态不是线程安全的(至少是它们的初始化)。 – Bwmat

+0

@Bwmat:真的,语言并不能保证它在C++ 11之前。但是gcc确实(甚至在C++ 11之前)。 –

+1

@Bwmat:http://gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html –

1

正如你已经发现C++标准并不保证“静态存储持续时间的非局部变量的动态初始化是在main的第一个语句之前完成的”。但是,GCC确实在执行main之前进行初始化,如How Initialization Functions Are Handled中所述。

唯一的问题是从加载了dlopen的共享库中初始化静态对象。这些只会在图书馆被加载时初始化,但你无能为力。