Инициализация статических полей класса. Ч4



Продолжение нелегендарной истории 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