11

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))为模板成员的初始化 yield warning: attributes after parenthesized initializer ignored,我不知道有关静态初始化的其他技巧。

提前感谢任何可以给我答案的人!

4

2 回答 2

3

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

此处的措辞旨在与共享库一起使用。因为共享库可以在 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.
  }
}
于 2013-12-21T06:53:09.610 回答
1

正如您已经发现的那样,C++ 标准并不能保证“具有静态存储持续时间的非局部变量的动态初始化在 main 的第一条语句之前完成”。但是,GCC 在执行 main 之前会执行此类初始化,如如何处理初始化函数中所述。

唯一的问题是从加载了dlopen. 这些只会在加载库时初始化,但您无能为力。

于 2013-12-21T06:36:18.647 回答