Предположим, что у нас есть следующее:
void print()
{
int a; // declaration
a = 9;
cout << a << endl;
}
int main ()
{
print();
}
Является устройство хранения данных для переменной выделенным, в данный момент функционируют, печать призвана основная или является этим, когда выполнение достигает объявления в функции?
Это очень сильно зависит от компилятора, но логически хранилище назначается, как только объявлена переменная.
Рассмотрим этот упрощенный пример 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: вы можете думать о своем пространстве как о выделенном самой первой строке исполняемого кода в вашем блоке (в паре {}).
Я подумал, что немного поправлю это с помощью пояснительных комментариев, так как это выбранный ответ.
Что касается построения объектов:
Создание происходит в точке объявления, а деструктор вызывается, когда объект выходит за пределы области видимости.
Но создание объектов и выделение памяти не обязательно должны совпадать.
Так же, как уничтожение объектов и освобождение памяти не обязательно должны совпадать.
Что касается фактического выделения памяти в стеке:
Я не знаю, но вы можете проверить с помощью следующего кода:
void f()
{
int y;
y = 0;//breakpoint here
int x[1000000];
}
Запустив этот код, вы можете увидеть, где происходит исключение, для меня в Visual Studio 2008 это происходит при вводе функции. Он никогда не достигает точки останова.
В качестве дополнения к ответу Брайана Р. Бонди: Достаточно легко провести несколько экспериментов, чтобы показать, как это работает, более подробно, чем исключение- ошибок пространства стека. Рассмотрим этот код:
#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).
Это будет зависеть от компилятора, но обычно переменная int a
будет размещена в стеке в то время, когда функция вызывается.
По крайней мере, в том виде, в котором это обычно реализуется, это нечто среднее между ними. Когда вы вызываете функцию, компилятор генерирует код для вызова функции, который оценивает параметры (если они есть) и помещает их в регистры или в стек. Затем, когда выполнение достигнет входа в функцию, на стеке будет выделено место для локальных переменных, и локальные переменные, нуждающиеся в инициализации, будут инициализированы. В этот момент может появиться код для сохранения регистров, используемых функцией, перетасовки значений, чтобы они попали в нужные регистры, и тому подобное. После этого начинает выполняться код тела функции.