Инициализация статических полей класса. Ч4
Продолжение нелегендарной истории static class members initialization. Предыдущие части тут, тут и тут.
Я немного наврал, когда сказал, что статические переменные и мемберы инициализируются до входа в main(). Как Эдгар отметил в своем комменте, на самом деле тут вот что:
Стандарт дает на откуп реализациям вопрос о том, в какой конкретно момент времени происходит динамическая инициализация глобальных объектов. Единственное ограничение, что инициализация должна произойти до любого неинициализирующего odr-use действия над неинлайн переменными и функциями, определенными в той же единице трансляции, где переменная собирается инициализироваться(немного духоты). То есть до любого действия по считыванию, записи, взятию адреса и созданию ссылки от переменной или функции.
Довольно сложно воспроизвести пример, когда инициализация происходит после main(), потому что мы на это напрямую не может повлиять. Поэтому может быть вот такой потенциальный пример. Он не про статические поля класса конкретно, но зато более наглядный.
В мейне мы говорим, что где-то определен массив интов и внутри главной функции мы печатаем его содержимое.
Проблема в том, что не понятно, произойдет ли в source.cpp инициализация array до вызова main() или после. Если после, то мы вполне можем накнуться на неинициализированную память, что UB.
Подливает масло в огонь вот такое утверждение:
То есть если никакие переменные и функции не используются в юните, где переменная должна инициализироваться, ее значение вообще может быть не установлено.
Так и происходит в source.cpp. Что с бо'льшей вероятностью приведет эту программу к фрилансерскому(нерабочему) состоянию. Однако популярные компиляторы стараются сгладить углы в этом моменте и даже в таком виде у вас в 99.9 случаев из 100 будет все в порядке. Что не отменяет потенциальную угрозу, но тем не менее.
Такие проблемы скорее свойственны программам, использующим шареные библиотеки, потому что они сами по себе в рантайме подгружаются и непонятно когда это происходит. Да и стандарт никак не упоминает эти библиотеки, поэтому здесь большой простор для фантазии и поведения реализации.
Avoid dangerous situations with no gain. Stay cool.
#cppcore
Продолжение нелегендарной истории static class members initialization. Предыдущие части тут, тут и тут.
Я немного наврал, когда сказал, что статические переменные и мемберы инициализируются до входа в main(). Как Эдгар отметил в своем комменте, на самом деле тут вот что:
It is implementation-defined whether
the dynamic initialization of
a non-block non-inline variable with
static storage duration is sequenced
before the first statement of main or
is deferred. If it is deferred, it
strongly happens before any
non-initialization odr-use of any
non-inline function or non-inline
variable defined in the same
translation unit as the variable to be
initialized. It is implementation-defined
in which threads and at which points in
the program such deferred dynamic
initialization occurs.
Стандарт дает на откуп реализациям вопрос о том, в какой конкретно момент времени происходит динамическая инициализация глобальных объектов. Единственное ограничение, что инициализация должна произойти до любого неинициализирующего odr-use действия над неинлайн переменными и функциями, определенными в той же единице трансляции, где переменная собирается инициализироваться(немного духоты). То есть до любого действия по считыванию, записи, взятию адреса и созданию ссылки от переменной или функции.
Довольно сложно воспроизвести пример, когда инициализация происходит после main(), потому что мы на это напрямую не может повлиять. Поэтому может быть вот такой потенциальный пример. Он не про статические поля класса конкретно, но зато более наглядный.
// header.hpp
struct Class {
Class() : array{1, 2, 3, -1} {}
int array[4];
};
//source.cpp
#include "header.hpp"
Class var;
// main.cpp
#include <cstdio>
#include "header.hpp"
extern Class var;
int main(void)
{
for (int i = 0; var.array[i] != -1; i++) {
printf("%d\n", i);
}
}
В мейне мы говорим, что где-то определен массив интов и внутри главной функции мы печатаем его содержимое.
Проблема в том, что не понятно, произойдет ли в source.cpp инициализация array до вызова main() или после. Если после, то мы вполне можем накнуться на неинициализированную память, что UB.
Подливает масло в огонь вот такое утверждение:
If no variable or function is odr-used
from a given translation unit, the
non-local variables defined in that
translation unit may never be initialized
То есть если никакие переменные и функции не используются в юните, где переменная должна инициализироваться, ее значение вообще может быть не установлено.
Так и происходит в source.cpp. Что с бо'льшей вероятностью приведет эту программу к фрилансерскому(нерабочему) состоянию. Однако популярные компиляторы стараются сгладить углы в этом моменте и даже в таком виде у вас в 99.9 случаев из 100 будет все в порядке. Что не отменяет потенциальную угрозу, но тем не менее.
Такие проблемы скорее свойственны программам, использующим шареные библиотеки, потому что они сами по себе в рантайме подгружаются и непонятно когда это происходит. Да и стандарт никак не упоминает эти библиотеки, поэтому здесь большой простор для фантазии и поведения реализации.
Avoid dangerous situations with no gain. Stay cool.
#cppcore