Отложенный порядок инициализации в C++11

Рассмотрим следующий код, разбитый на три модуля компиляции:

a.h:

struct A
{
    void Register(const char* s);

    const char* m_s[10];
    int m_i = 0;
};

A& GetA();

a.cpp:

#include "a.h"
#include <stdio.h>

void A::Register(const char* s)
{
    m_s[m_i++] = s;
}

A& GetA()
{
    static A instance;
    return instance;
}

int main(int argc, char* argv[])
{
    A& a = GetA();
    int n = a.m_i;
    for (int i = 0; i < n ; ++i)
        printf("%s\n", a.m_s[i]);
    return 0;
}

b.cpp:

#include "a.h"
struct B
{
    B() { GetA().Register("b"); }

    static B* instance;
};
B* B::instance = new B;

c.cpp:

#include "a.h"
struct C
{
    C() { GetA().Register("c"); }

    static C* instance;
};
C* C::instance = new C;

Код строится и работает нормально, используя gcc (-std= C++11), создавая вывод:

c
b

Теперь, ссылаясь на cppreference.com:

Отложенная динамическая инициализация

Это определяется реализацией, происходит ли динамическая инициализация перед первым оператором основной функции (для статики) или начальной функции потока (для локальных потоков), или откладывается на выполнение после.

Если инициализация не встроенной переменной откладывается на выполнение после первого оператора функции main/thread, это происходит до первого использования odr любой переменной со статическим / потоковым хранением, заданным в той же единице перевода, что и переменная для быть инициализирован. Если ни одна переменная или функция не используется odr из данного модуля перевода, нелокальные переменные, определенные в этом модуле перевода, могут никогда не инициализироваться (это моделирует поведение динамической библиотеки по требованию). Однако, пока что-либо из TU используется odr, все нелокальные переменные, инициализация или уничтожение которых имеет побочные эффекты, будут инициализироваться, даже если они не используются в программе.

Обратите внимание, что a.cpp не знает о существовании B а также Cи что единственные взаимодействия B & C с A являются вызовами GetA() а также A::Register() во время строительства их соответствующих экземпляров.

Насколько я вижу, B & C случаи не используются ODR, и, конечно, не из main()переводческий блок. Их инициализация явно имеет побочные эффекты, но мне кажется, что нет никакой гарантии, что эта инициализация произойдет до входа в main()или раньше main() печатает зарегистрированные строки - или даже вообще.

Итак, наконец, мой вопрос заключается в следующем: является ли тот факт, что B а также C экземпляры инициализируются раньше main() печатает зарегистрированные строки не из-за стандарта, а из-за поведения, определенного в gcc?

Если это гарантировано стандартом, то как?

1 ответ

Решение

Является ли тот факт, что B а также C экземпляры инициализируются раньше main() печатает зарегистрированные строки не из-за стандарта, а из-за поведения, определенного в gcc?

Это не гарантируется стандартом. Наиболее актуальная часть цитаты:

Если ни одна переменная или функция не используется odr из заданной единицы перевода, нелокальные переменные, определенные в этой единице перевода, никогда не могут быть инициализированы

Поскольку ни функция, ни переменная не использовались из b.cpp ни c.cppих статические переменные могут быть неинициализированы (в отношении динамической инициализации), и, следовательно, побочные эффекты их инициализации могут быть не видны.


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

Другие вопросы по тегам