17

今天早上我和一位同事讨论了静态变量初始化顺序。他提到了Nifty/Schwarz 柜台,我(有点)困惑。我了解它是如何工作的,但我不确定这在技术上是否符合标准。

假设以下 3 个文件(前两个是从More C++ Idioms复制粘贴的):


//Stream.hpp
class StreamInitializer;

class Stream {
   friend class StreamInitializer;
 public:
   Stream () {
   // Constructor must be called before use.
   }
};
static class StreamInitializer {
  public:
    StreamInitializer ();
    ~StreamInitializer ();
} initializer; //Note object here in the header.

//Stream.cpp
static int nifty_counter = 0; 
// The counter is initialized at load-time i.e.,
// before any of the static objects are initialized.
StreamInitializer::StreamInitializer ()
{
  if (0 == nifty_counter++)
  {
    // Initialize Stream object's static members.
  }
}
StreamInitializer::~StreamInitializer ()
{
  if (0 == --nifty_counter)
  {
    // Clean-up.
  }
}

// Program.cpp
#include "Stream.hpp" // initializer increments "nifty_counter" from 0 to 1.

// Rest of code...
int main ( int, char ** ) { ... }

...这就是问题所在!有两个静态变量:

  1. “漂亮的计数器”在Stream.cpp;和
  2. 中的“初始化程序” Program.cpp

由于这两个变量恰好位于两个不同的编译单元中,因此没有(AFAIK)官方保证在调用构造函数nifty_counter之前初始化为 0 。initializer

我可以将两个快速解决方案视为“有效”的两个原因:

  1. 现代编译器足够聪明,可以解决两个变量之间的依赖关系,并将代码以适当的顺序放置在可执行文件中(极不可能);
  2. nifty_counter实际上就像文章所说的那样在“加载时”初始化,并且它的值已经放在可执行文件的“数据段”中,所以它总是在“任何代码运行之前”被初始化(很有可能)。

在我看来,这两者都依赖于一些非官方但可能的实现。这个标准是否符合标准,或者这只是“很可能起作用”以至于我们不应该担心它?

4

2 回答 2

22

我相信它可以保证工作。根据标准 ($3.6.2/1):“在进行任何其他初始化之前,具有静态存储持续时间 (3.7.1) 的对象应进行零初始化 (8.5)。”

由于nifty_counter具有静态存储持续时间,因此它在创建之前被初始化initializer,无论翻译单元之间的分布如何。

编辑:在重新阅读有问题的部分并考虑@Tadeusz Kopec 评论的输入后,我不太确定它现在是否定义良好,但确保它定义良好非常微不足道的:删除的定义初始化nifty_counter,所以它看起来像:

static int nifty_counter;

由于它具有静态存储持续时间,因此即使没有指定初始化程序,它也会被零初始化 - 删除初始化程序可以消除对在零初始化之后发生的任何其他初始化的任何疑问。

于 2011-04-11T14:33:10.407 回答
3

我认为这个例子中缺少的是如何避免构建 Stream,这通常是不可移植的。除了漂亮的计数器之外,初始化器的作用是构造类似的东西:

extern Stream in;

如果一个编译单元具有与该对象关联的内存,在使用就地 new 运算符之前是否有一些特殊的构造函数,或者在我看到的情况下,内存以另一种方式分配以避免任何冲突。在我看来,这个流上有一个无操作构造函数,然后是先调用初始化程序还是没有定义无操作构造函数的顺序。

分配一个字节区域通常是不可移植的,例如对于 gnu iostream,cin 的空间定义为:

typedef char fake_istream[sizeof(istream)] __attribute__ ((aligned(__alignof__(istream))))
...
fake_istream cin;

llvm 使用:

_ALIGNAS_TYPE (__stdinbuf<char> ) static char __cin [sizeof(__stdinbuf <char>)];

两者都对对象所需的空间做出了一定的假设。Schwarz Counter 使用新位置初始化的位置:

new (&cin) istream(&buf)

实际上,这看起来并不那么便携。

我注意到一些编译器,如 gnu、microsoft 和 AIX,确实有编译器扩展来影响静态初始化程序的顺序:

  • 对于 Gnu,这是:使用标志启用init-priority-f使用__attribute__ ((init_priority (n))).
  • 在带有 microsoft 编译器的 Windows 上,有一个 #pragma ( http://support.microsoft.com/kb/104248 )
于 2013-08-21T22:04:06.467 回答