Как инициализация статической переменной осуществляется компилятором? - PullRequest
20 голосов
/ 22 мая 2009

Мне любопытно узнать о базовой реализации статических переменных внутри функции.

Если я объявляю статическую переменную фундаментального типа (char, int, double и т. Д.) И задаю ей начальное значение, я думаю, что компилятор просто устанавливает значение этой переменной в самом начале программы до того, как main() будет вызвано:

void SomeFunction();

int main(int argCount, char ** argList)
{
    // at this point, the memory reserved for 'answer'
    // already contains the value of 42
    SomeFunction();
}

void SomeFunction()
{
    static int answer = 42;
}

Однако, если статическая переменная является экземпляром класса:

class MyClass
{
    //...
};

void SomeFunction();

int main(int argCount, char ** argList)
{
    SomeFunction();
}

void SomeFunction()
{
    static MyClass myVar;
}

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

static bool initialized = 0;
if (!initialized)
{
    // construct myVar
    initialized = 1;
}

Ответы [ 5 ]

12 голосов
/ 22 мая 2009

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

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

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

11 голосов
/ 22 мая 2009

Этот вопрос касался аналогичного вопроса, но безопасность резьбы не упоминалась. Несмотря на это, C ++ 0x сделает поток статической инициализации функции безопасным.

(см. C ++ 0x FCD , 6.7 / 4 о статических функциях: «Если управление вводит объявление одновременно во время инициализации переменной, параллельное выполнение должно ожидать завершение инициализации. ")

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

2 голосов
/ 22 мая 2009

Вы правы во всем, включая инициализированный флаг в качестве общей реализации. Именно поэтому инициализация статических локальных объектов не является поточно-ориентированной, и поэтому существует pthread_once.

Одно небольшое предостережение: компилятор должен выдавать код, который "ведет себя так, как будто" статическая локальная переменная создается при первом использовании. Поскольку целочисленная инициализация не имеет побочных эффектов (и не вызывает пользовательский код), это зависит от компилятора, когда он инициализирует int. Пользовательский код не может «законно» выяснить, что он делает.

Очевидно, что вы можете посмотреть на ассемблерный код или спровоцировать неопределенное поведение и сделать выводы из того, что действительно происходит. Но стандарт C ++ не считает это веским основанием утверждать, что поведение не «как если бы», а то, что сказано в спецификации.

1 голос
/ 22 мая 2009

Другой поворот заключается во встроенном коде, где код run-before-main () (cinit / что угодно) может копировать предварительно инициализированные данные (как статические, так и нестатические) в оперативную память из сегмента константных данных, возможно, находящегося в ПЗУ. Это полезно, когда код может не запускаться из какого-либо резервного хранилища (диска), откуда он может быть перезагружен. Опять же, это не нарушает требований языка, так как это делается до main ().

Небольшая касательная: хотя я и не видел, чтобы это было сделано много (за пределами Emacs), программа или компилятор могли бы в основном запустить ваш код в процессе и создать / инициализировать объекты, а затем заморозить и сбросить процесс. Emacs делает что-то похожее на это, загружая большое количество elisp (т.е. пережевывая его), а затем сбрасывая состояние выполнения как рабочий исполняемый файл, чтобы избежать затрат на синтаксический анализ при каждом вызове.

1 голос
/ 22 мая 2009

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

Да, это верно: и, FWIW, это не обязательно потокобезопасно (если функция вызывается «впервые» двумя потоками одновременно).

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

...