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

StackOverflow https://stackoverflow.com/questions/55510

  •  09-06-2019
  •  | 
  •  

Вопрос

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

int globalgarbage;
unsigned int anumber = 42;

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

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

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

Это было полезно?

Решение

Мне это было интересно, поэтому я написал следующую тестовую программу и скомпилировал ее с помощью 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

Другие советы

Некоторые соответствующие формулировки из стандарта C++:

3.6.2 Инициализация нелокальных объектов [basic.start.init]

1

Хранилище для объектов со статической продолжительностью хранения (Basic.stc.static) должно быть инициализировано нулем (dcl.init) перед какой -либо другой инициализацией произойдет.Объекты типов стручков (основные.типы) со статической продолжительностью хранения, инициализированной с постоянными выражениями (выражение.const) должен быть инициализирован до того, как произойдет любая динамическая инициализация.Объекты области пространства имен со статической продолжительностью хранения, определенной в том же блоке перевода и динамически инициализированной, должны быть инициализированы в порядке, в котором их определение появляется в блоке перевода.[Примечание: dcl.init.aggr описывает порядок, в котором инициализируются совокупные члены.Инициализация локальных статических объектов описана в stmt.dcl. ]

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

6.7 Декларация [stmt.dcl]

...

4

Нулевая инициализация (dcl.init) всех локальных объектов со статической продолжительностью хранения (Basic.stc.static) выполняется до того, как произойдет любая другая инициализация.Локальный объект типа POD (основные.типы) со статической продолжительностью хранения, инициализированной с постоянными экспрессиями, инициализируется до того, как его блок будет впервые введен.Реализация разрешена выполнять раннюю инициализацию других локальных объектов со статической продолжительностью хранения в тех же условиях, что и реализация разрешена статически инициализировать объект со статической продолжительностью хранения в области пространства имен (основной.start.init).В противном случае такой объект инициализируется, что управление первым временем проходит через его объявление;Такой объект считается инициализированным после завершения его инициализации.Если инициализация выходит из -за исключения, инициализация не завершена, поэтому она будет опробована еще раз в следующий раз, когда контроль входит в объявление.Если контроль повторно входит в объявление (рекурсивно), когда объект инициализируется, поведение не определен.[Пример:

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

--конечный пример]

5

Деструктор для локального объекта со статической продолжительностью хранения будет выполняться, если и только тогда, когда переменная была построена.[Примечание: основной.start.term Описывает порядок, в котором локальные объекты со статической продолжительностью хранения разрушаются.]

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

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

@Адам:Это скрытое внедрение кода компилятором и является причиной результата, который вы видели.

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

  • Вывод (для g++, среды Windows):

    1. Глобальная переменная и статический член в классе:конструктор вызывается перед вводом основной функция (1).
    2. Локальная статическая переменная:Конструктор вызывается только тогда, когда выполнение достигает своего объявления в первый раз.
    3. Если Локальная статическая переменная имеет тип POD., то он также инициализируется перед вводом основной функция (1).Пример для типа POD: статическое целое число = 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?

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top