Счетчик Nifty / Schwarz, соответствует стандарту? - PullRequest
16 голосов
/ 11 апреля 2011

Сегодня утром я обсуждал с коллегой порядок инициализации статических переменных. Он упомянул счетчик Nifty / Schwarz , и я (вроде) озадачен. Я понимаю, как это работает, но я не уверен, что это, технически говоря, стандартное соответствие.

Предположим, что 3 следующих файла (первые два - copy-pasta'd из 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. "nifty_counter" в Stream.cpp; и
  2. «инициализатор» в Program.cpp.

Поскольку две переменные находятся в двух разных единицах компиляции, (AFAIK) Официальный не гарантирует, что nifty_counter инициализируется в 0 до вызова конструктора initializer.

Я могу думать о двух быстрых решениях как о двух, почему это «работает»:

  1. современные компиляторы достаточно умны, чтобы разрешить зависимость между двумя переменными и разместить код в соответствующем порядке в исполняемом файле (весьма маловероятно);
  2. nifty_counter фактически инициализируется в «время загрузки», как говорится в статье, и его значение уже помещено в «сегмент данных» в исполняемом файле, поэтому оно всегда инициализируется «перед выполнением любого кода» (очень вероятно).

Мне кажется, что оба они зависят от неофициальной, но возможной реализации. Является ли этот стандарт совместимым или это просто "настолько вероятно", что мы не должны беспокоиться об этом?

Ответы [ 2 ]

21 голосов
/ 11 апреля 2011

Я верю, что это гарантированно работает. В соответствии со стандартом ($ 3.6.2 / 1): «Объекты со статической продолжительностью хранения (3.7.1) должны быть инициализированы нулями (8.5) перед любой другой инициализацией».

Поскольку nifty_counter имеет статическую продолжительность хранения, он инициализируется до создания initializer, независимо от распределения по единицам перевода.

Редактировать: После перечитывания рассматриваемого раздела и рассмотрения входных данных из комментария @Tadeusz Kopec, я менее уверен в том, хорошо ли он определен в его нынешнем виде, но является довольно тривиальным для обеспечения что он четко определен: удалите инициализацию из определения nifty_counter, чтобы она выглядела так:

static int nifty_counter;

Поскольку он имеет статическую длительность хранения, он будет инициализирован нулем, даже без указания инициализатора, а удаление инициализатора устраняет любые сомнения относительно любой другой инициализации, происходящей после инициализации нуля.

3 голосов
/ 22 августа 2013

Я думаю, что в этом примере отсутствует то, как избежать создания Stream, это часто непереносимо. Помимо изящного счетчика, роль инициализатора заключается в создании чего-то вроде:

extern Stream in;

Если одна единица компиляции имеет память, связанную с этим объектом, есть ли какой-то специальный конструктор перед использованием нового оператора на месте, или в случаях, которые я видел, память выделялась другим способом, чтобы избежать конфликтов , Мне кажется, что если в этом потоке есть конструктор без операции, то порядок того, вызывается ли инициализатор первым или конструктор без операции, не определен.

Для выделения области байтов часто непереносимо, например, для 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 это: Включите init-priority с флагом -f и используйте __attribute__ ((init_priority (n))).
  • В Windows с компилятором Microsoft есть #pragma (http://support.microsoft.com/kb/104248)
...