Nifty/Schwarz 计数器,符合标准吗?

发布于 2024-10-31 11:18:41 字数 1753 浏览 1 评论 0原文

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

假设以下 3 个文件(前两个是从 更多 C++ 习语复制粘贴的):


//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 中的“nifty_counter”;和
  2. Program.cpp 中的“初始化程序”。

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

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

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

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

I had a discussion this morning with a colleague about static variable initialization order. He mentioned the Nifty/Schwarz counter and I'm (sort of) puzzled. I understand how it works, but I'm not sure if this is, technically speaking, standard compliant.

Suppose the 3 following files (the first two are copy-pasta'd from 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 ** ) { ... }

... and here lies the problem! There are two static variables:

  1. "nifty_counter" in Stream.cpp; and
  2. "initializer" in Program.cpp.

Since the two variables happen to be in two different compilation units, there is no (AFAIK) official guarantee that nifty_counter is initialized to 0 before initializer's constructor is called.

I can think of two quick solutions as two why this "works":

  1. modern compilers are smart enough to resolve the dependency between the two variables and place the code in the appropriate order in the executable file (highly unlikely);
  2. nifty_counter is actually initialized at "load-time" like the article says and its value is already placed in the "data segment" in the executable file, so it is always initialized "before any code is run" (highly likely).

Both of these seem to me like they depend on some unofficial, yet possible implementation. Is this standard compliant or is this just "so likely to work" that we shouldn't worry about it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

空城缀染半城烟沙 2024-11-07 11:18:41

我相信它一定能发挥作用。根据标准 ($3.6.2/1):“具有静态存储持续时间 (3.7.1) 的对象应在发生任何其他初始化之前进行零初始化 (8.5)。”

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

编辑:在重读了相关部分并考虑了 @Tadeusz Kopec 评论的输入后,我不太确定它是否按照目前的情况定义良​​好,但确保它非常微不足道定义良好:从 nifty_counter 的定义中删除初始化,因此它看起来像:

static int nifty_counter;

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

I believe it's guaranteed to work. According to the standard ($3.6.2/1): "Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place."

Since nifty_counter has static storage duration, it gets initialized before initializer is created, regardless of distribution across translation units.

Edit: After rereading the section in question, and considering input from @Tadeusz Kopec's comment, I'm less certain about whether it's well defined as it stands right now, but it is quite trivial to ensure that it is well-defined: remove the initialization from the definition of nifty_counter, so it looks like:

static int nifty_counter;

Since it has static storage duration, it will be zero-initialized, even without specifying an intializer -- and removing the initializer removes any doubt about any other initialization taking place after the zero-initialization.

澉约 2024-11-07 11:18:41

我认为这个例子中缺少的是如何避免 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>)];

两者都对对象所需的空间做出某些假设。施瓦茨计数器用新的放置进行初始化:

new (&cin) istream(&buf)

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

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

  • 对于 Gnu,这是:使用 -finit-priority > 标记并使用 __attribute__ ((init_priority (n))) 。
  • 在带有 Microsoft 编译器的 Windows 上,有一个 #pragma (http://support.microsoft.com/kb/104248 )

I think missing from this example is how the construction of Stream is avoided, this often is non-portable. Besides the nifty counter the initialisers role is to construct something like:

extern Stream in;

Where one compilation unit has the memory associated with that object, whether there is some special constructor before the in-place new operator is used, or in the cases I've seen the memory is allocated in another way to avoid any conflicts. It seems to me that is there is a no-op constructor on this stream then the ordering of whether the initialiser is called first or the no-op constructor is not defined.

To allocate an area of bytes is often non-portable for example for gnu iostream the space for cin is defined as:

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

llvm uses:

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

Both make certain assumption about the space needed for the object. Where the Schwarz Counter initialises with a placement new:

new (&cin) istream(&buf)

Practically this doesn't look that portable.

I've noticed that some compilers like gnu, microsoft and AIX do have compiler extensions to influence static initialiser order:

  • For Gnu this is: Enable the init-priority with the -f flag and use __attribute__ ((init_priority (n))).
  • On windows with a microsoft compiler there is a #pragma (http://support.microsoft.com/kb/104248)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文