Подтвердить что ты не робот

Гарантируется инициализация статического объекта

Я пытаюсь узнать об инициализации статических объектов. Статическая инициализация кажется довольно прямой, если вы понимаете постоянные выражения и constexpr. Динамическая инициализация кажется несколько более сложной.

[basic.start.init]/4

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

сноска 34

Нелокальная переменная со статической продолжительностью хранения, имеющей инициализацию с побочными эффектами, должна быть инициализирована, даже если она не используется в качестве odr (3.2, 3.7.1).

[basic.start.init]/5

Реализовано определение того, выполняется ли динамическая инициализация нелокальной переменной со статической или длительностью хранения потоков до первого утверждения начальной функции потока. Если инициализация отложена до некоторого момента времени после первого утверждения начальной функции потока, она должна произойти до первого использования odr (3.2) любой переменной с продолжительностью хранения потоков, определенной в той же самой единицы перевода, что и переменная для инициализации.

Я предполагаю, что "начальная функция потока" относится к основным, а не только к потокам, запущенным с помощью std:: thread.

h1.h

#ifndef H1_H_
#define H1_H_

extern int count;

#endif

tu1.cpp

#include "h1.h"

struct S
{
   S()
   {
      ++count;
   }
};

S s;

tu2.cpp

#include "h1.h"

int main(int argc, char *argv[])
{
   return count;
}

tu3.cpp

#include "h1.h"

int count;

Итак, если компилятор отменил динамическую инициализацию, кажется, что в сноске 34 указано, что s необходимо инициализировать в какой-то момент. Поскольку в блоке трансляции нет других переменных с динамической инициализацией, нет никакой другой переменной для использования odr для принудительной инициализации переменных в tu1. В какой момент s гарантированно был инициализирован?

Гарантировано ли возвращение 1? Кроме того, есть ли способ изменить эту программу, чтобы больше не было гарантировано вернуть 1? В качестве альтернативы, если это не гарантировано, есть ли способ изменить эту программу таким образом, чтобы она гарантировалась?


Я сломал код так, чтобы определение s находилось в другой единицы перевода от main. Это позволяет избежать вопроса о том, используется ли main odr. Учитывая, что s является единственным объектом в блоке перевода, гарантируется, что main вернет 1?

4b9b3361

Ответ 1

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

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

  • должен быть инициализирован до первого использования odr в чем-либо в его блоке перевода;
  • возможно, перед запуском main, но возможно после него.

Я интерпретирую сноску 34 как (но помните, что сноски не являются нормативными):

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

Итак, если есть TU, где ничего не используется, то его динамические инициализации могут не произойти.

Пример

h1.h

extern int count;
struct S
{
    S();
};

h1.cpp

#include "h1.h"

int count;
S::S()
{
   ++count;
}

h2.cpp

#include "h1.h"
S s;

main.cpp

#include "h1.h"
#include <stdio.h>
int main()
{
    printf("%d\n", count);
}

Это может печатать 0 или 1: поскольку ничего в TU h2 никогда не используется odr, оно не указывается, когда инициализация кода s будет выполнена, если вообще.

Естественно, что синтаксические компиляторы инициализируют s до main, поэтому он обязательно напечатает 1:

$ g++ main.cpp h2.cpp h1.cpp -o test1
$ ./test1
1

Теперь представьте, что h2.cpp находится в общей библиотеке:

$ g++ -shared -fPIC h2.cpp -o h2.so

И главный файл теперь:

main2.cpp

#include "h1.h"
#include <dlfcn.h>
#include <stdio.h>

int main()
{
    printf("%d\n", count);
    dlopen("./h2.so", RTLD_NOW);
    printf("%d\n", count);
    return 0;
}

Скомпилировать и запустить:

$ g++ -shared -fPIC h2.cpp -o h2.so
$ g++ -rdynamic main.cpp h1.cpp -ldl -o test2
$ ./test2
0
1

См? Инициализация s была отложена! Приятная часть состоит в том, что невозможно ссылаться на что-либо в динамически загружаемой библиотеке, не загружая сначала ее, а загрузка будет инициировать динамическую инициализацию. Так что все хорошо.

Если вы считаете, что использование dlopen изменяет, помните, что есть компиляторы, которые поддерживают задержку загрузки разделяемых библиотек (например, VС++), где системный вызов для загрузки библиотеки будет сгенерирован автоматически компилятором в первый раз это необходимо.

Ответ 2

Без поиска правильных страниц в определении я могу сказать, что ваша программа гарантированно вернется 1. Каждая статическая или глобальная инициализация выполняется до первой команды в основном. Сначала глобальные переменные инициализируются, затем выполняются конструкторы глобальных объектов. Статичность в рамках функции/метода имеет значение для инициализации перед первым использованием. Но есть ловушка:

int count;

struct A
{
   A()
   {
     count=5;
   }
};

struct B
{
   B()
   {
     count=count*2;
   }
};


A a;
B b;

void main(void)
{
  return count;
}

Как упоминалось в комментарии Ben Voigt, результат определяется, если оба экземпляра создаются в одной и той же единицы перевода. Таким образом, в моем примере результат равен 10. Если экземпляры создаются в разных файлах (и скомпилированы отдельно для разных файлов .obj), результат не определен.

Ответ 3

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