Який термін служби статичної змінної у функції 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
Якщо функцію можна викликати декількома потоками, то це означає, що вам потрібно переконатися, що статичні декларації повинні бути захищені mutex в C ++ 98 ??
allyourcode

1
"деструктори" глобальних об'єктів повинні працювати в зворотному порядку завершення їх побудови "тут не застосовується, оскільки ці об'єкти не є глобальними. Порядок знищення місцевих жителів зі статичною тривалістю або зберіганням ниток значно складніше, ніж чистий LIFO, див. Розділ 3.6.3[basic.start.term]
Бен Войгт

2
Фраза "при припиненні програми" не є строго правильною. А що з статикою у dll Windows, які динамічно завантажуються та завантажуються? Очевидно, що стандарт C ++ взагалі не стосується складання (було б добре, якби це було), але уточнення навколо того, що саме тут говорить стандарт, було б добре. Якщо словосполучення "при завершенні програми" було включено, технічно було б зробити будь-яку реалізацію C ++ з динамічно розвантаженими збірками невідповідними.
Роджер Сандерс

2
@Motti Я не вірю, що стандарт явно дозволяє динамічні бібліотеки, але до цього часу я також не вірив, що в стандарті щось конкретно не суперечить його реалізації. Звичайно, строго кажучи мова тут не вказує, що статичні об'єкти не можна знищувати раніше за допомогою інших засобів, просто що вони повинні бути знищені при поверненні з головного або виклику std :: exit. Дуже тонка лінія, хоча я думаю.
Роджер Сандерс

125

Мотті має рацію щодо порядку, але слід врахувати ще деякі речі:

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

Якщо ви маєте локальну статику, як описано вище, і fooвикликається з декількох потоків, у вас можуть бути умови перегонів, plonkякі можуть ініціалізуватися неправильно або навіть кілька разів. Також у цьому випадку plonkможе бути зруйнований інший потік, ніж той, який його сконструював.

Незважаючи на те, що говорить стандарт, я б дуже насторожено ставився до фактичного порядку локального статичного знищення, оскільки можливо, ви можете мимоволі розраховувати на те, що статика все ще діє після її руйнування, і це справді важко відстежити.


68
C ++ 0x вимагає, щоб статична ініціалізація була безпечною для потоків. Тож будьте обережні, але все стане тільки на краще.
deft_code

Питання порядку знищення можна уникнути за допомогою невеликої політики. статичні / глобальні об'єкти (одиночні кнопки тощо) не мають доступу до інших статичних об'єктів у їхніх методах. Доступ до них здійснюється лише у конструкторах, де посилання / покажчик можуть бути збережені для подальшого доступу до методів. Це не є ідеальним, але слід виправити 99 випадків, а випадки, які він не спіймає, очевидно, є рибними, і їх слід потрапити в огляд коду. Це все ще не є ідеальним виправленням, оскільки політику неможливо застосувати на мові
deft_code

Я трохи ноб, але чому ця політика не може бути застосована мовою?
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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.