Когда статические переменные уровня функции выделяются / инициализируются?

91

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

int globalgarbage;
unsigned int anumber = 42;

Но как насчет статических, определенных в функции?

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

Когда выделяется место под globalish? Я догадываюсь, когда программа запустится. Но инициализируется ли он тоже? Или инициализируется при doSomething()первом вызове?

Оуэн
источник

Ответы:

93

Мне это было интересно, поэтому я написал следующую тестовую программу и скомпилировал ее с 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
Адам Пирс
источник
31
В качестве пояснения: статическая переменная инициализируется при первом выполнении ее объявления, а не при вызове содержащей ее функции. Если у вас просто статика в начале функции (например, в вашем примере), это то же самое, но не обязательно: например, если у вас есть 'if (...) {static MyClass x; ...} ', то' x 'не будет инициализирован ВСЕГО во время первого выполнения этой функции в случае, когда условие оператора if оценивается как ложное.
EvanED
4
Но не приводит ли это к накладным расходам времени выполнения, поскольку каждый раз, когда используется статическая переменная, программа должна проверять, использовалась ли она ранее, поскольку в противном случае ее нужно инициализировать? В таком случае это немного отстой.
HelloGoodbye
идеальная иллюстрация
Des1gnWizard
@veio: Да, инициализация потокобезопасна. См. Этот вопрос для более подробной информации: stackoverflow.com/questions/23829389/…
Реми
2
@HelloGoodbye: да, это приводит к накладным расходам времени выполнения. Также см. Этот
Реми
54

Некоторое релевантное словоблудие из стандарта 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
не совсем так, локальная статика выделяется и инициализируется нулем «при загрузке программы» (в кавычках, потому что это тоже не совсем правильно), а затем повторно инициализируется при первом входе в функцию, в которой они находятся.
Mooing Duck
Похоже, эта ссылка разорвана 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: static int number = 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?

Тханг Ле
источник
4

Или он инициализируется при первом вызове 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. При первом вызове переменная будет инициализирована. Затем при первом и следующем вызовах будет возвращено его значение (по ссылке).

дмитюгов
источник
3

Статические переменные размещаются внутри сегмента кода - они являются частью исполняемого образа и поэтому отображаются в уже инициализированном виде.

Статические переменные в области видимости функции обрабатываются одинаково, область видимости является исключительно конструкцией уровня языка.

По этой причине вам гарантируется, что статическая переменная будет инициализирована значением 0 (если вы не укажете что-то еще), а не неопределенным значением.

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

В C ++ (с глобальным охватом) у статических объектов конструкторы вызываются как часть запуска программы под управлением библиотеки времени выполнения C. В Visual C ++ по крайней мере порядок, в котором инициализируются объекты, можно контролировать с помощью прагмы init_seg .

Роб Уокер
источник
4
Этот вопрос касается статики в функциональной области. По крайней мере, когда у них есть нетривиальные конструкторы, они инициализируются при первом входе в функцию. Или, точнее, когда эта линия достигнута.
Адам Миц,
Верно, но вопрос касается пространства, выделенного для переменной, и использует простые типы данных. Пространство по-прежнему выделено в кодовом сегменте
Роб Уокер
Я не понимаю, какое значение имеет здесь сегмент кода и сегмент данных. Я думаю, нам нужны пояснения из ОП. Он сказал «и инициализировал, если применимо».
Адам Миц
5
переменные никогда не размещаются внутри сегмента кода; таким образом они не будут доступны для записи.
botismarius
1
статическим переменным выделяется место в сегменте данных или в сегменте bss в зависимости от того, инициализированы они или нет.
EmptyData