Примечание: Этот ответ way слишком длинный. Я порежу это когда-нибудь. Тем временем, прокомментируйте, если можете придумать полезные правки.
Чтобы ответить на ваши вопросы, нам сначала нужно определить две области памяти, называемые стек и куча .
Стек
Представьте себе стек как набор ящиков. Каждый блок представляет выполнение функции. В начале, когда вызывается main
, на полу сидит одна коробка. Все локальные переменные, которые вы определяете, находятся в этом поле.
Простой пример
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return a + b;
}
В этом случае у вас есть один блок на этаже с переменными argc
(целое число), argv
(указатель на массив символов), a
(целое число) и b
( целое число).
Более одной коробки
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
int c = a + b;
c++;
return c;
}
Теперь у вас есть коробка на полу (для main
) с argc
, argv
, a
и b
. Сверху этого ящика у вас есть еще один ящик (для do_stuff
) с a
, b
и c
.
Этот пример иллюстрирует два интересных эффекта.
Как вы, наверное, знаете, a
и b
были переданы по значению. Вот почему в поле есть копия этих переменных для do_stuff
.
Обратите внимание, что вам не нужно free
или delete
или что-либо еще для этих переменных. Когда ваша функция вернется, поле для этой функции будет уничтожено.
Переполнение коробки
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
return do_stuff(a, b);
}
Здесь у вас есть коробка на полу (для main
, как и раньше). Затем у вас есть ящик (для do_stuff
) с a
и b
. Затем у вас есть еще одна коробка (для do_stuff
вызывающего себя), снова с a
и b
. А потом еще. И скоро у вас есть стек переполнение .
Сводка стека
Думайте о стеке как о стеке коробок. Каждый блок представляет выполняемую функцию, и этот блок содержит локальные переменные, определенные в этой функции. Когда функция возвращается, этот ящик уничтожается.
Больше технических вещей
- Каждый «ящик» официально называется стековым фреймом .
- Когда-нибудь замечали, как ваши переменные имеют "случайные" значения по умолчанию? Когда старый стековый фрейм «уничтожен», он просто перестает быть актуальным. Это не обнуляется или что-то в этом роде. В следующий раз, когда кадр стека использует этот раздел памяти, вы увидите биты старого кадра стека в ваших локальных переменных.
Куча
Здесь вступает в игру динамическое распределение памяти.
Представьте себе кучу бесконечного зеленого луга памяти. Когда вы вызываете malloc
или new
, блок памяти выделяется в куче. Вам дан указатель для доступа к этому блоку памяти.
int main(int argc, char * argv[])
{
int * a = new int;
return *a;
}
Здесь в куче выделяется новое целое число памяти. Вы получите указатель с именем a
, который указывает на эту память.
a
- локальная переменная, и поэтому она находится в main
'box'
Обоснование динамического выделения памяти
Конечно, использование динамически выделяемой памяти, кажется, тратит несколько байтов здесь и там для указателей. Однако есть вещи, которые вы просто не можете (легко) сделать без динамического выделения памяти.
Возвращение массива
int main(int argc, char * argv[])
{
int * intarray = create_array();
return intarray[0];
}
int * create_array()
{
int intarray[5];
intarray[0] = 0;
return intarray;
}
Что здесь происходит? Вы "возвращаете массив" в create_array
. В действительности вы возвращаете указатель, который просто указывает на часть create_array
«поля», содержащего массив. Что происходит, когда create_array
возвращается? Его коробка уничтожена, и вы можете ожидать, что ваш массив будет поврежден в любой момент.
Вместо этого используйте динамически выделяемую память.
int main(int argc, char * argv[])
{
int * intarray = create_array();
int return_value = intarray[0];
delete[] intarray;
return return_value;
}
int * create_array()
{
int * intarray = new int[5];
intarray[0] = 0;
return intarray;
}
Поскольку функция возврата не изменяет кучу, ваш драгоценный intarray
не может остаться невредимым. Не забудьте delete[]
сделать это после того, как вы закончите.