Каково время жизни статической переменной в функции C ++?

373

Если переменная объявлена ​​как staticв области действия функции, она инициализируется только один раз и сохраняет свое значение между вызовами функции. Что именно это время жизни? Когда вызывается его конструктор и деструктор?

void foo() 
{ 
    static string plonk = "When will I die?";
}
Моти
источник

Ответы:

257

Время жизни функциональных staticпеременных начинается в первый раз, когда [0] поток программы встречает объявление, и заканчивается при завершении программы. Это означает, что среда выполнения должна вести какую-то бухгалтерию, чтобы разрушить ее только в том случае, если она действительно была построена.

Кроме того, поскольку в стандарте говорится, что деструкторы статических объектов должны запускаться в порядке, обратном завершению их построения [1] , и порядок построения может зависеть от конкретного запуска программы, необходимо учитывать порядок построения. ,

пример

struct emitter {
    string str;
    emitter(const string& s) : str(s) { cout << "Created " << str << endl; }
    ~emitter() { cout << "Destroyed " << str << endl; }
};

void foo(bool skip_first) 
{
    if (!skip_first)
        static emitter a("in if");
    static emitter b("in foo");
}

int main(int argc, char*[])
{
    foo(argc != 2);
    if (argc == 3)
        foo(false);
}

Вывод:

C:> sample.exe
Создано в foo
Уничтожено в foo

C:> sample.exe 1
Создан, если
Создан в foo
Разрушен в foo
Разрушен в if

C:> sample.exe 1 2
Создан в foo
Создан, если
уничтожен, если
уничтожен в foo

[0]Поскольку C ++ 98 [2] не имеет ссылки на несколько потоков, то, как это будет вести себя в многопоточной среде, не определено, и это может быть проблематично, как упоминает Родди .

[1] C ++ 98 раздел 3.6.3.1 [basic.start.term]

[2]В C ++ 11 статика инициализируется потокобезопасным способом, это также называется Magic Statics .

Моти
источник
2
Для простых типов без побочных эффектов c'tor / d'tor, это простая оптимизация, чтобы инициализировать их так же, как глобальные простые типы. Это позволяет избежать проблем ветвления, флага и порядка уничтожения. Это не значит, что их жизнь отличается.
Джон Макфарлейн
1
Если функция может вызываться несколькими потоками, значит ли это, что вам нужно убедиться, что статические объявления должны быть защищены мьютексом в C ++ 98 ??
allyourcode
1
«Деструкторы» глобальных объектов должны запускаться в порядке, обратном завершению их построения »здесь не применимо, поскольку эти объекты не являются глобальными. Порядок уничтожения локальных пользователей со статическим или длительным хранилищем значительно сложнее, чем у чистого LIFO, см. Раздел 3.6.3[basic.start.term]
Бен Фойгт,
2
Фраза «при завершении программы» не совсем верна. Как насчет статики в Windows, которые загружаются и выгружаются динамически? Очевидно, что стандарт C ++ вообще не имеет отношения к сборкам (было бы неплохо, если бы он это сделал), но было бы неплохо прояснить, что именно здесь говорится в стандарте. Если включить фразу «при завершении программы», это технически сделает любую реализацию C ++ с динамически выгруженными сборками несоответствующей.
Роджер Сандерс
2
@ Мотти: Я не верю, что стандарт явно разрешает динамические библиотеки, но до сих пор я также не верил, что в стандарте есть что-то конкретное, что противоречит его реализации. Конечно, строго говоря, язык здесь не утверждает, что статические объекты не могут быть уничтожены ранее с помощью других средств, просто они должны быть уничтожены при возврате из main или вызове std :: exit. Я думаю, довольно тонкая грань.
Роджер Сандерс
125

Мотти прав насчет порядка, но есть некоторые другие вещи, которые следует учитывать:

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

Если у вас есть локальная статика, как указано выше, и она fooвызывается из нескольких потоков, могут возникнуть условия гонки, приводящие plonkк неправильной инициализации или даже к нескольким разам. Кроме того, в этом случае plonkможет быть разрушен другой поток, чем тот, который его создал.

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

Родди
источник
68
C ++ 0x требует, чтобы статическая инициализация была поточно-ориентированной. Так что будьте осторожны, но все будет только лучше.
deft_code 7.10.10
Проблемы с порядком уничтожения можно избежать с помощью небольшой политики. статические / глобальные объекты (синглтоны и т. д.) не должны иметь доступа к другим статическим объектам в своих телах методов. Они должны быть доступны только в конструкторах, где ссылка / указатель могут быть сохранены для последующего доступа в методах. Это не идеально, но должно исправить 99 случаев, и случаи, которые он не обнаруживает, явно сомнительны и должны быть обнаружены в обзоре кода. Это все еще не идеальное решение, так как политика не может быть применена на языке
deft_code 7.10.10
Я немного новичок, но почему нельзя применить эту политику на языке?
cjcurrie
9
Начиная с C ++ 11, это больше не проблема. Ответ Мотти обновляется в соответствии с этим.
Ниланджан Басу
10

Существующие объяснения на самом деле не полны без действительного правила из Стандарта, найденного в 6.7:

Инициализация нуля всех переменных области блока со статической продолжительностью хранения или продолжительностью хранения потока выполняется перед любой другой инициализацией. Постоянная инициализация объекта области блока со статической продолжительностью хранения, если применимо, выполняется перед первым вводом его блока. Реализации разрешается выполнять раннюю инициализацию других переменных области блока со статической или длительностью хранения потока при тех же условиях, что реализации разрешается статически инициализировать переменную со статической или продолжительностью хранения потока в области пространства имен. В противном случае такая переменная инициализируется при первом прохождении контроля через ее объявление; такая переменная считается инициализированной после завершения ее инициализации. Если инициализация завершается с помощью исключения, инициализация не завершена, поэтому она будет повторена при следующем входе элемента управления в объявление. Если элемент управления вводит объявление одновременно с инициализацией переменной, параллельное выполнение должно ожидать завершения инициализации. Если элемент управления повторно вводит объявление рекурсивно во время инициализации переменной, поведение не определено.

Бен Фойгт
источник
8

FWIW, Codegear C ++ Builder не разрушает в ожидаемом порядке в соответствии со стандартом.

C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if

... что является еще одной причиной не полагаться на порядок уничтожения!

Родди
источник
57
Не хороший аргумент. Я бы сказал, что это скорее аргумент, чтобы не использовать этот компилятор.
Мартин Йорк,
26
Хм. Если вы заинтересованы в создании реального переносимого кода, а не только теоретически переносимого кода, я думаю, что полезно знать, какие области языка могут вызвать проблемы. Я был бы удивлен, если C ++ Builder уникален тем, что не справляется с этим.
Родди
17
Я бы согласился, за исключением того, что я бы сформулировал это так: «какие компиляторы вызывают проблемы и в каких областях языка они это делают» ;-P
Стив Джессоп
0

В переменных Статическом пришел в игру , как только начинает выполнение программы и остается доступными до концов выполнения программы.

Статические переменные создаются в сегменте данных памяти .

Чандра Шекхар
источник
это не относится к переменным в области действия функции
awerries