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

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

void foo() 
{ 
    static string plonk = "When will I die?";
}

person Motti    schedule 29.10.2008    source источник


Ответы (5)


Время жизни переменных функции 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
Создано в if
Создано в foo
Уничтожено в foo
Уничтожено в if

C: ›sample.exe 1 2
Создано в foo
Создано в if
Уничтожено в if
Уничтожено в foo

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

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

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

person Motti    schedule 29.10.2008
comment
Для простых типов, не имеющих побочных эффектов c'tor / d'tor, можно выполнить прямую оптимизацию, чтобы инициализировать их так же, как глобальные простые типы. Это позволяет избежать проблем с ветвлением, флагом и порядком уничтожения. Это не значит, что их продолжительность жизни какая-то другая. - person John McFarlane; 29.10.2011
comment
Если функция может быть вызвана несколькими потоками, значит ли это, что вам нужно убедиться, что статические объявления должны быть защищены мьютексом в C ++ 98 ?? - person allyourcode; 09.10.2014
comment
@allyourcode, я обновил ответ, чтобы сослаться на C ++ 11, относительно мьютекса в C ++ 98, да. - person Motti; 10.10.2014
comment
деструкторы глобальных объектов должны выполняться в порядке, обратном завершению их построения, здесь не применяется, поскольку эти объекты не являются глобальными. Порядок уничтожения локальных переменных со статической или поточной продолжительностью хранения значительно сложнее, чем чистый LIFO, см. Раздел 3.6.3 [basic.start.term] - person Ben Voigt; 16.06.2015
comment
@BenVoigt, спасибо за исправление, но из того, что я вижу в C ++ 98, это в значительной степени LIFO. C ++ 11 немного усложняет thread_local вещи. - person Motti; 16.06.2015
comment
Фраза при завершении программы не совсем правильная. А как насчет статики в DLL Windows, которые загружаются и выгружаются динамически? Очевидно, что стандарт C ++ вообще не касается сборок (было бы неплохо, если бы это было так), но было бы хорошо уточнить, что именно здесь говорится в стандарте. Если бы фраза при завершении программы была включена, это технически сделало бы любую реализацию C ++ с динамически выгружаемыми сборками несоответствующей. - person Roger Sanders; 14.04.2017
comment
@RogerSanders, это хороший аргумент, стандарт, который у меня есть (C ++ 11), говорит, что деструкторы объектов со статической продолжительностью хранения вызываются в результате возврата из main и в результате вызова std::exit . Можете ли вы указать мне на часть стандарта, где явно разрешены динамические библиотеки? - person Motti; 17.04.2017
comment
@Motti Я не верю, что стандарт явно разрешает использование динамических библиотек, но до сих пор я также не верил, что в стандарте есть что-то конкретное, что противоречит его реализации. Конечно, строго говоря, язык здесь не утверждает, что статические объекты нельзя уничтожить раньше другими способами, просто они должны быть уничтожены при возврате из main или вызове std :: exit. Хотя, я думаю, это довольно тонкая грань. - person Roger Sanders; 28.04.2017
comment
Ваш первый абзац верен только для объектов с непустой инициализацией. Для других объектов (например, static int x = 1;) время жизни начинается с момента получения хранилища, то есть при запуске программы для объектов со статической продолжительностью хранения (basic.life/1) - person M.M; 09.12.2018

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

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

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

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

person Roddy    schedule 29.10.2008
comment
C ++ 0x требует, чтобы статическая инициализация была потокобезопасной. Так что будьте осторожны, но все будет только лучше. - person deft_code; 07.10.2010
comment
Проблем с порядком уничтожения можно избежать с помощью небольшой политики. статические / глобальные объекты (синглтоны и т. д.) не должны обращаться к другим статическим объектам в своих телах методов. Доступ к ним должен осуществляться только в конструкторах, где ссылка / указатель может быть сохранена для последующего доступа в методах. Это не идеально, но должно исправить 99 случаев, а случаи, которые он не улавливает, явно подозрительны и должны быть обнаружены при проверке кода. Это по-прежнему не идеальное решение, так как политика не может быть применена на языке - person deft_code; 07.10.2010
comment
Я немного новичок, но почему эту политику нельзя применять на языке? - person cjcurrie; 21.01.2013
comment
Начиная с C ++ 11, это больше не проблема. Ответ Мотти обновлен в соответствии с этим. - person Nilanjan Basu; 28.12.2017

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

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

person Ben Voigt    schedule 11.09.2014

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

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

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

person Roddy    schedule 29.10.2008
comment
Не лучший аргумент. Я бы сказал, что это скорее аргумент не использовать этот компилятор. - person Martin York; 29.10.2008
comment
Хм. Если вы заинтересованы в создании реального переносимого кода, а не просто теоретически переносимого кода, я думаю, полезно знать, какие области языка могут вызывать проблемы. Я был бы удивлен, если бы C ++ Builder не справился с этой задачей. - person Roddy; 29.10.2008
comment
Я согласен, за исключением того, что я бы сформулировал это как то, какие компиляторы вызывают проблемы и в каких областях языка они это делают ;-P - person Steve Jessop; 30.10.2008

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

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

person Chandra Shekhar    schedule 13.06.2019
comment
это неверно для переменных в области действия функции - person awerries; 01.06.2020