В каком точном моменте локальная переменная является выделенным устройством хранения данных?

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

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

int main ()
{
     print();
}

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

9
задан Brandon 1 March 2010 в 05:07
поделиться

5 ответов

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

Рассмотрим этот упрощенный пример 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, для возвращаемого значения не требуется никакой работы. Команда «выйти» разрушает кадр стека, а команда «ret» возвращает управление вызывающей стороне.

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


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

9
ответ дан 4 December 2019 в 11:04
поделиться

Что касается построения объектов:

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

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

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

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

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

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

  int x[1000000];
}

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

3
ответ дан 4 December 2019 в 11:04
поделиться

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

#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
ответ дан 4 December 2019 в 11:04
поделиться

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

2
ответ дан 4 December 2019 в 11:04
поделиться

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

2
ответ дан 4 December 2019 в 11:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: