




Время жизни переменных функции 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
Created in foo
Destroyed in fooC:>sample.exe 1
Created in if
Created in foo
Destroyed in foo
Destroyed in ifC:>sample.exe 1 2
Created in foo
Created in if
Destroyed in if
Destroyed in foo
[0] Поскольку C++ 98[2] не имеет ссылки на несколько потоков, как это будет вести себя в многопоточной среде, не указано и может быть проблематичным, как упоминает Родди.
[1]C++ 98 раздел 3.6.3.1[basic.start.term]
[2] В C++ 11 статика инициализируется поточно-безопасным способом, это также известно как Магическая статика.
Если функция может быть вызвана несколькими потоками, значит ли это, что вам нужно убедиться, что статические объявления должны быть защищены мьютексом в C++ 98 ??
@allyourcode, я обновил ответ, чтобы сослаться на C++ 11, относительно мьютекса в C++ 98, Ага.
«деструкторы» глобальных объектов должны выполняться в порядке, обратном завершению их построения »здесь не применяется, потому что эти объекты не являются глобальными. Порядок уничтожения локальных переменных со статической или поточной продолжительностью хранения значительно сложнее, чем чистый LIFO, см. Раздел 3.6.3 [basic.start.term]
@BenVoigt, спасибо за исправление, но из того, что я вижу в C++ 98, это в значительной степени LIFO. C++ 11 немного усложняет работу с thread_local.
Фраза «при завершении программы» не совсем правильна. А как насчет статики в DLL Windows, которые загружаются и выгружаются динамически? Очевидно, что стандарт C++ вообще не касается сборок (было бы неплохо, если бы это было так), но было бы хорошо уточнить, что именно здесь говорится в стандарте. Если бы была включена фраза «при завершении программы», это технически сделало бы любую реализацию C++ с динамически выгружаемыми сборками несоответствующей.
@RogerSanders, это хороший момент, стандарт, который у меня есть (C++ 11), говорит, что деструкторы объектов со статической продолжительностью хранения вызываются в результате возврата из main и в результате вызова std::exit. Можете ли вы указать мне на часть стандарта, где явно разрешены динамические библиотеки?
@Motti Я не верю, что стандарт явно разрешает использование динамических библиотек, но до сих пор я также не верил, что в стандарте есть что-то конкретное, что противоречит его реализации. Конечно, строго говоря, язык здесь не утверждает, что статические объекты нельзя уничтожить раньше другими способами, просто они должны быть уничтожены при возврате из main или вызове std :: exit. Я считаю, что это довольно тонкая грань.
Ваш первый абзац верен только для объектов с непустой инициализацией. Для других объектов (например, static int x = 1;) время жизни начинается с момента получения хранилища, то есть при запуске программы для объектов со статической продолжительностью хранения (basic.life/1)
Мотти прав насчет порядка, но есть еще кое-что, что следует учитывать:
Компиляторы обычно используют скрытую переменную флага, чтобы указать, была ли локальная статика уже инициализирована, и этот флаг проверяется при каждой записи функции. Очевидно, это небольшой удар по производительности, но больше беспокоит то, что потокобезопасность этого флага не гарантируется.
Если у вас есть локальная статика, как указано выше, и foo вызывается из нескольких потоков, у вас могут быть условия гонки, вызывающие неправильную или даже многократную инициализацию plonk. Кроме того, в этом случае plonk может быть разрушен потоком, отличным от того, который его создал.
Несмотря на то, что говорится в стандарте, я бы очень осторожно относился к фактическому порядку локального статического разрушения, потому что возможно, что вы можете невольно полагаться на статическое состояние, которое остается действительным после его уничтожения, и это действительно сложно отследить.
C++ 0x требует, чтобы статическая инициализация была потокобезопасной. Так что будьте осторожны, но все будет только лучше.
Проблем с порядком уничтожения можно избежать с помощью небольшой политики. статические / глобальные объекты (одиночные объекты и т. д.) не должны обращаться к другим статическим объектам в своих телах методов. Доступ к ним должен осуществляться только в конструкторах, где ссылка / указатель может быть сохранена для последующего доступа в методах. Это не идеально, но должно исправить 99 случаев, а случаи, которые он не улавливает, явно сомнительны и должны быть обнаружены при проверке кода. Это по-прежнему не идеальное решение, так как политика не может быть применена на языке
Я немного новичок, но почему эту политику нельзя применять на языке?
Начиная с C++ 11, это больше не проблема. Ответ Мотти обновлен в соответствии с этим.
FWIW, Codegear C++ Builder не выполняет деструкцию в ожидаемом порядке согласно стандарту.
C:\> sample.exe 1 2
Created in foo
Created in if
Destroyed in foo
Destroyed in if
... что является еще одной причиной не полагаться на приказ об уничтожении!
Не лучший аргумент. Я бы сказал, что это скорее аргумент не использовать этот компилятор.
Хм. Если вы заинтересованы в создании реального переносимого кода, а не просто теоретически переносимого кода, я думаю, полезно знать, какие области языка могут вызывать проблемы. Я был бы удивлен, если бы C++ Builder не справился с этой задачей.
Я согласен, за исключением того, что я бы сформулировал это как «какие компиляторы вызывают проблемы и в каких областях языка они это делают» ;-P
Существующие объяснения на самом деле не полны без фактического правила из Стандарта, найденного в 6.7:
The zero-initialization of all block-scope variables with static storage duration or thread storage duration is performed before any other initialization takes place. Constant initialization of a block-scope entity with static storage duration, if applicable, is performed before its block is first entered. An implementation is permitted to perform early initialization of other block-scope variables with static or thread storage duration under the same conditions that an implementation is permitted to statically initialize a variable with static or thread storage duration in namespace scope. Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.
Статические переменные вступают в игру после запуск программы и остаются доступными до завершения выполнения программы.
Статические переменные создаются в Сегмент данных в памяти.
это неверно для переменных в области действия функции
Для простых типов, не имеющих побочных эффектов c'tor / d'tor, можно выполнить прямую оптимизацию, чтобы инициализировать их так же, как глобальные простые типы. Это позволяет избежать проблем с ветвлением, флагом и порядком уничтожения. Это не значит, что их продолжительность жизни какая-то другая.