Различия между динамической памятью и «обычной» памятью - PullRequest
11 голосов
/ 20 июня 2009

Каковы некоторые технические различия между памятью, которая выделяется оператором new, и памятью, которая выделяется посредством простого объявления переменной, такого как int var? Есть ли в c ++ автоматическое управление памятью?

В частности, у меня есть пара вопросов. Во-первых, поскольку с динамической памятью вам нужно объявить указатель для хранения адреса фактической памяти, с которой вы работаете, разве динамическая память не использует больше памяти? Я не понимаю, почему указатель нужен вообще, если вы не объявляете массив.

Во-вторых, если бы я сделал простую функцию, такую ​​как эта:

int myfunc() { int x = 2; int y = 3; return x+y; }

... И назовите это, освободится ли память, выделенная функцией, как только закончится ее существование? А как насчет динамической памяти?

Ответы [ 5 ]

20 голосов
/ 20 июня 2009

Примечание: Этот ответ 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.

Этот пример иллюстрирует два интересных эффекта.

  1. Как вы, наверное, знаете, a и b были переданы по значению. Вот почему в поле есть копия этих переменных для do_stuff.

  2. Обратите внимание, что вам не нужно 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[] сделать это после того, как вы закончите.

4 голосов
/ 20 июня 2009

Память, выделенная "новым", попадает в кучу.

Память, выделенная в функции, находится внутри функции, в которой функция размещена в стеке.

Читайте о стеке и распределении кучи здесь: http://www -ee.eng.hawaii.edu / ~ tep / EE160 / Book / chap14 / subsection2.1.1.8.html

4 голосов
/ 20 июня 2009

Динамическая память живет в куче, а не в стеке. Время жизни динамической памяти - от момента выделения до момента освобождения. Для локальных переменных их время жизни ограничено функцией / блоком, в котором они определены.

Что касается вашего вопроса об использовании памяти в функции, в вашем примере память для ваших локальных переменных будет освобождена в конце функции. Однако, если память была динамически выделена с помощью new, она не будет автоматически удалена, и вы будете нести ответственность за явное использование delete для освобождения памяти.

Что касается автоматического управления памятью, стандартная библиотека C ++ предоставляет для этого auto_ptr.

2 голосов
/ 20 июня 2009

Память, выделенная новым оператором, извлекается из раздела памяти, называемого «куча», в то время как статические выделения для переменных используют раздел памяти, совместно используемый с вызовами процедур / функций («стек»).

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

1 голос
/ 20 июня 2009

Большая разница между «динамической» и «обычной» памятью довольно хорошо отражена в самом вопросе.

Динамическая память не слишком хорошо поддерживается C ++.

Когда вы используете динамическую память, вы сами несете за нее полную ответственность. Вы должны выделить это. Когда вы забудете сделать это и попытаетесь получить к нему доступ, вы получите множество негативных сюрпризов. Также вам нужно освободить память - и когда вы ее забудете, у вас будет еще больше сюрпризов. Такие ошибки относятся к наиболее сложным ошибкам в программах на C / C ++.

Вам нужен дополнительный указатель, поскольку каким-то образом вам нужен доступ к вашей новой памяти. Некоторая память (динамическая или нет) - это прежде всего то, с чем не может справиться язык программирования. Вы должны иметь доступ к нему. Это делается с помощью переменных. Но переменные в таких языках, как C ++, хранятся в «обычной» памяти. Поэтому вам нужно иметь «указатели» - указатели являются формой косвенного обращения, которая говорит: «Нет, я не та ценность, которую вы ищете, но я указываю на это». Указатели являются единственной возможностью в C ++ для доступа к динамической памяти.

Напротив, «обычная» память может быть доступна напрямую, выделение и освобождение выполняется автоматически самим языком.

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

По этой же причине во многих библиотеках есть функции или целые модули для работы с динамической памятью. Пример auto_ptr также упоминался в параллельном ответе, который пытается решить проблему: динамическая память должна быть надежно освобождена в конце метода.

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

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