В какой именно момент локальная переменная выделяет хранилище? - PullRequest
9 голосов
/ 01 марта 2010

Предположим, у нас есть следующее:

void print()
{
     int a;  // declaration
     a = 9;
     cout << a << endl;
}

int main ()
{
     print();
}

Является ли хранилище для переменной, выделенной в данный момент, функцией print, вызывается в main или это происходит, когда выполнение достигает объявления внутри функции?

Ответы [ 5 ]

9 голосов
/ 01 марта 2010

Это очень сильно зависит от компилятора, но логически хранилище назначается, как только объявлена ​​переменная.

Рассмотрим этот упрощенный пример C ++:

// junk.c++
int addtwo(int a)
{
    int x = 2;

    return a + x;
}

Когда GCC компилирует это, генерируется следующий код (; комментарии мои ):

.file   "junk.c++"
    .text
.globl _Z6addtwoi
    .type   _Z6addtwoi, @function
_Z6addtwoi:
.LFB2:
    pushl   %ebp           ;store the old stack frame (caller's parameters and locals)
.LCFI0:
    movl    %esp, %ebp     ;set up the base pointer for our parameters and locals
.LCFI1:
    subl    $16, %esp      ;leave room for local variables on the stack
.LCFI2:
    movl    $2, -4(%ebp)   ;store the 2 in "x" (-4 offset from the base pointer)
    movl    -4(%ebp), %edx ;put "x" into the DX register
    movl    8(%ebp), %eax  ;put "a" (+8 offset from base pointer) into AX register
    addl    %edx, %eax     ;add the two together, storing the results in AX
    leave                  ;tear down the stack frame, no more locals or parameters
    ret                    ;exit the function, result is returned in AX by convention
.LFE2:
    .size   _Z6addtwoi, .-_Z6addtwoi
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

Все, что находится между _Z6addtwoi и .LCFI2, является стандартным кодом, используемым для установки стекового фрейма (надежно храните переменные предыдущей функции и т. Д.). Последний «subl $ 16,% esp» - это распределение локальной переменной x.

.LCFI2 - это первый бит введенного вами кода. «movl $ 2, -4 (% ebp)» помещает значение 2 в переменную. (Инициализация, другими словами.) Теперь ваше пространство выделено И инициализировано. После этого он загружает значение в регистр EDX и затем перемещает ваш параметр, найденный в «8 (% ebp)», в другой регистр EAX. Затем они складывают их вместе, оставляя результат в EAX. Теперь это конец любого кода, который вы на самом деле набрали. Остальное опять просто шаблон. Поскольку GCC требует, чтобы целые числа возвращались в EAX, для возвращаемого значения не требуется никакой работы. Инструкция «exit» разрушает кадр стека, а инструкция «ret» возвращает управление обратно вызывающей стороне.

TL; DR сводка: вы можете думать о том, что ваше пространство выделено самой первой строкой исполняемого кода в вашем блоке (в паре {}).


Я подумал, что немного исправлю это с помощью пояснительных комментариев, поскольку это выбранный ответ.

3 голосов
/ 01 марта 2010

В качестве дополнения к ответу Брайана Р. Бонди: достаточно просто провести несколько экспериментов, чтобы показать, как это работает, чуть более подробно, чем выбрасывать ошибки из стека из пространства. Рассмотрим этот код:

#include<iostream>

void foo()
{
  int e; std::cout << "foo:e " << &e << std::endl;
}

int main()
{
  int a; std::cout << "a: " << &a << std::endl;
  foo();
  int b; std::cout << "b: " << &b << std::endl;
  {
    int c; std::cout << "c: " << &c << std::endl;
    foo();
  }
  int d; std::cout << "d: " << &d << std::endl;
}

Это выдаст такой вывод на моей машине:

$ ./stack.exe
a: 0x28cd30
foo:e 0x28cd04
b: 0x28cd2c
c: 0x28cd24
foo:e 0x28cd04
d: 0x28cd28

Так как стек растет вниз, мы можем видеть порядок, в котором вещи помещаются в стек: a, b, d и c в этом порядке, а затем два вызова foo () помещают его e в один и тот же поместите оба раза. Это означает, что один и тот же объем памяти был выделен в стеке каждый раз, когда вызывается функция foo (), несмотря на то, что несколько объявлений переменных (включая одно во внутренней области видимости) вмешиваются. Таким образом, в этом случае мы можем сделать вывод, что вся память стека для локальных переменных в main () была выделена в начале main (), а не увеличивалась постепенно.

Вы также можете видеть, что компилятор упорядочивает вещи так, что конструкторы вызываются в порядке убывания стека, а деструкторы вызываются в порядке возрастания - все это нижняя построенная вещь в стеке, когда она создается и когда он разрушен, но это не означает, что это нижняя часть, для которой было выделено пространство, или что в стеке нет неиспользуемого пространства над ним для вещей, которые не были построены пока (например, пространство для d, когда c или два воплощения foo: e построены).

3 голосов
/ 01 марта 2010

Что касается строительства объектов:

Построение происходит в точке объявления, и деструктор вызывается, когда объект выходит из области видимости.

Но построение объектов и когда выделяется память не должны совпадать.

Так же, как уничтожение объектов и когда память освобождается, совпадать не нужно.

Что касается того, когда память в стеке фактически выделяется:

Не знаю, но вы можете проверить с помощью следующего кода:

void f()
{
  int y;
  y = 0;//breakpoint here

  int x[1000000];
}

Запустив этот код, вы увидите, где происходит исключение, для меня в Visual Studio 2008 это происходит при входе в функцию. Он никогда не достигает точки останова.

2 голосов
/ 01 марта 2010

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

2 голосов
/ 01 марта 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...