Коли статичні змінні на рівні функції розподіляються / ініціалізуються?


89

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

int globalgarbage;
unsigned int anumber = 42;

Але як щодо статичних, визначених у функції?

void doSomething()
{
  static bool globalish = true;
  // ...
}

Коли globalishвиділяється місце? Я здогадуюсь, коли програма почнеться. Але чи він також ініціалізується? Або він ініціалізується при doSomething()першому виклику?

Відповіді:


91

Мені було цікаво з цього приводу, тому я написав таку тестову програму та скомпілював її з g ++ версії 4.1.2.

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

Результати були не такими, як я очікував. Конструктор для статичного об'єкта не викликався до першого виклику функції. Ось результат:

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed

30
Як роз’яснення: статична змінна ініціалізується при першому виконанні, коли потрапляє до її оголошення, а не при виклику функції, що містить. Якщо у вас просто є статичний на початку функції (наприклад, у вашому прикладі), вони однакові, але не обов'язково: наприклад, якщо у вас є 'if (...) {static MyClass x; ...} ', тоді' x 'не буде ініціалізовано ВСЕ під час першого виконання цієї функції у випадку, коли умова оператора if оцінюється як false.
EvanED

4
Але чи це не призводить до накладних витрат на виконання, оскільки кожного разу, коли використовується статична змінна, програма повинна перевіряти, чи використовувалась вона раніше, оскільки, якщо ні, вона повинна бути ініціалізована? У такому випадку такий вид відстой трохи.
HelloGoodbye

ідеальна ілюстрація
Des1gnWizard

@veio: Так, ініціалізація захищена від потоків. Подивіться це запитання для отримання більш докладної інформації: stackoverflow.com/questions/23829389/…
Ремі

2
@HelloGoodbye: так, це веде до накладних витрат. Також дивіться це запитання: stackoverflow.com/questions/23829389/…
Ремі

53

Деякі відповідні слова із стандарту C ++:

3.6.2 Ініціалізація нелокальних об’єктів [basic.start.init]

1

Сховище для об'єктів із статичною тривалістю зберігання ( basic.stc.static ) повинно бути нульово ініціалізоване ( dcl.init ) перед будь-якою іншою ініціалізацією. Об'єкти типів POD ( basic.types ) зі статичною тривалістю зберігання, ініціалізовані константними виразами ( expr.const ), повинні бути ініціалізовані до будь-якої динамічної ініціалізації. Об'єкти простору імен із статичною тривалістю зберігання, визначеною в одному блоці перекладу та динамічно ініціалізованою, ініціалізується в тому порядку, в якому їх визначення відображається в блоці перекладу. [Примітка: dcl.init.aggr описує порядок ініціалізації сукупних членів. Ініціалізація локальних статичних об’єктів описана в stmt.dcl . ]

[далі текст нижче, додаючи більше свобод для авторів компіляторів]

6.7 Заява про декларацію [stmt.dcl]

...

4

Нульова ініціалізація ( dcl.init ) усіх локальних об’єктів зі статичною тривалістю зберігання ( basic.stc.static ) виконується до того, як відбудеться будь-яка інша ініціалізація. Локальний об'єкт типу POD ( basic.types ) зі статичною тривалістю зберігання, ініціалізований константними виразами, ініціалізується перед тим, як його блок вводиться вперше. Реалізація має змогу виконувати ранню ініціалізацію інших локальних об’єктів зі статичною тривалістю зберігання за тих самих умов, що реалізація дозволяє статично ініціалізувати об’єкт зі статичною тривалістю зберігання в області простору імен ( basic.start.init). В іншому випадку такий об'єкт ініціалізується, коли елемент управління вперше проходить через його оголошення; такий об'єкт вважається ініціалізованим після завершення його ініціалізації. Якщо ініціалізація виходить, викинувши виняток, ініціалізація не завершена, тому її буде спробувано знову, коли наступний раз елемент управління введе декларацію. Якщо елемент керування повторно вводить декларацію (рекурсивно) під час ініціалізації об’єкта, поведінка не визначена. [ Приклад:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- кінцевий приклад ]

5

Деструктор для локального об'єкта зі статичною тривалістю зберігання буде виконаний тоді і тільки тоді, коли змінна була побудована. [Примітка: basic.start.term описує порядок знищення локальних об’єктів із статичною тривалістю зберігання. ]


Це відповіло на моє запитання і не покладається на "анекдотичні докази" на відміну від прийнятої відповіді. Я спеціально шукав цю згадку про винятки у конструкторі статично ініціалізованих функцій локальних статичних об'єктів: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.
Bensge,

26

Пам'ять для всіх статичних змінних виділяється при завантаженні програми. Але локальні статичні змінні створюються та ініціалізуються при першому використанні, а не під час запуску програми. Там якась - то гарне читання про це, і статика в цілому, тут . Загалом, я думаю, що деякі з цих питань залежать від реалізації, особливо якщо ви хочете знати, де в пам'яті будуть розташовані ці речі.


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

Схоже, це посилання порушено через 7 років.
Стів

1
Так, посилання обірвано. Ось архів: web.archive.org/web/20100328062506/http://www.acm.org/…
Євген

10

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

@Adam: Це кулуарне введення коду компілятором є причиною результату, який ви побачили.


5

Я намагаюся ще раз протестувати код від Адама Пірса і додав ще два випадки: статичну змінну в класі та тип POD. Мій компілятор - g ++ 4.8.1, в ОС Windows (MinGW-32). Результат - статична змінна в класі, обробляється так само, як і глобальна змінна. Його конструктор буде викликаний перед введенням основної функції.

  • Висновок (для g ++, середовища Windows):

    1. Глобальна змінна та статичний член у класі : конструктор викликається перед введенням основної функції (1) .
    2. Локальна статична змінна : конструктор викликається лише тоді, коли виконання досягає свого оголошення в перший раз.
    3. Якщо локальна статична змінна має тип POD , то вона також ініціалізується перед введенням основної функції (1) . Приклад для типу POD: статичне число int = 10;

(1) : Правильний стан повинен бути: "перед викликом будь-якої функції з тієї самої одиниці перекладу". Однак для простої, як у прикладі нижче, це головна функція.

включити <iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

результат:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

Хтось тестував у Linux env?


3

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

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

З цієї причини вам гарантовано, що статична змінна буде ініціалізована значенням 0 (якщо ви не вкажете щось інше), а не невизначеним значенням.

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

У C ++ (у глобальному масштабі) статичні об'єкти мають свої конструктори, що викликаються як частина запуску програми під контролем бібліотеки середовища виконання C. У Visual C ++ принаймні порядок ініціалізації об'єктів може контролюватися прагмою init_seg .


4
Це питання стосується статики з функціональним масштабом. Принаймні, коли вони мають нетривіальні конструктори, вони ініціалізуються при першому вході у функцію. Або більш конкретно, коли ця лінія досягнута.
Адам Міц,

Правда - але питання говорить про простір, виділений для змінної, і використовує прості типи даних. Простір все ще виділено в сегменті коду
Роб Уолкер,

Я не розумію, як тут насправді має значення сегмент коду проти сегмента даних. Я думаю, нам потрібні роз’яснення з ОП. Він дійсно сказав "та ініціалізується, якщо застосовно".
Адам Міц,

5
змінні ніколи не виділяються всередині сегмента коду; таким чином вони не могли б писати.
botismarius

1
статичним змінним виділяється простір у сегменті даних або в сегменті bss залежно від того, ініціалізовані вони чи ні.
EmptyData

3

Або він ініціалізується при першому виклику doSomething ()?

Так. Це, серед іншого, дозволяє ініціалізувати глобально доступні структури даних, коли це доречно, наприклад всередині блоків try / catch. Наприклад, замість

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

ти можеш писати

int& foo() {
  static int myfoo = init();
  return myfoo;
}

і використовувати його всередині блоку try / catch. Під час першого виклику змінна буде ініціалізована. Потім під час першого та наступного викликів буде повернуто його значення (за посиланням).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.