2011-12-14 2 views
6

Я не совсем понимаю динамически выделенную память, и я надеюсь, что вы, ребята, можете сделать все более понятным для меня.C++ динамически распределенная память

Прежде всего, каждый раз, когда мы выделяем память, мы просто получаем указатель на эту память.

int * dynInt = new int; 

Так что разница между делать то, что я сделал выше и:

int someInt; 
int* dynInt = &someInt; 

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

Так в чем же разница между ними. Когда один метод предпочтительнее другого.

Более того, почему мне нужно, чтобы освободить память с

delete dynInt; 

в первом случае, но не во втором случае.

Моих догадок:

  1. Когда динамическое выделение памяти для объекта, объект не инициализированы, а если вы делаете что-то вроде во втором случае объект прибудет инициализируются. Если это единственное отличие, есть ли какая-то мотивация, кроме того, что динамическое распределение памяти происходит быстрее.

  2. Причина, по которой нам не нужно использовать удаление для второго случая, состоит в том, что тот факт, что объект был инициализирован, создает какую-то рутину автоматического уничтожения.

Это просто догадки, если бы кто-то меня исправил и уточнил для меня.

+3

Если вы не уверены, следуйте простому правилу: «Никогда не используйте указатели, никогда не используйте« новый ». Как только вы поймете необходимость в ресурсе, управляемом вручную, вы узнаете, когда нарушить это правило. –

+1

Umm Могу ли я настоятельно предложить вам открыть книгу о C++ .. области переменных, управление динамической памятью, как правило, обсуждаются в 4-й или 5-й главе любых книг начинающих C++. @KerrekSB Я бы не сказал, что .. Особенно в этом случае неопределенность не исходит от амбициозности, а вместо этого недостаточно знаний - если вы не уверены, убедитесь, что вы достаточно читаете, чтобы вы были уверены. – paul23

+0

Динамическое выделение памяти обычно _slower_, а инициализация не имеет ничего общего с этим. –

ответ

15

Разница в Срок хранения.

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

    Создайте их как int someInt;

    Возможно, вы слышали о них как о «предметах стека», хотя я объект этой терминологии.

  • Объекты с динамической продолжительностью хранения имеют что-то вроде «ручной» жизни; вы должны уничтожить их самостоятельно delete и создать их с помощью ключевого слова new.

    Возможно, вы слышали о них как о «кучных объектах», хотя я и возражаю против этого.

Использование указателей на самом деле не имеет отношения к любому из них. У вас может быть указатель на объект с продолжительностью автоматического хранения (второй пример), и вы можете иметь указатель на объект с динамической продолжительностью хранения (ваш первый пример).

Но это редко, что вы хотите указатель на автоматический объект, потому что:

  1. вы не один «по умолчанию»;
  2. Объект не будет длиться очень длинный, поэтому не может быть много чего вы можете сделать do с таким указателем.

В отличие от этого, динамические объекты часто обращаются через указатели, просто потому, что синтаксис близок к обеспечению его соблюдения. new возвращает указатель для использования, вы должны передать указатель на delete, и (кроме использования ссылок) на самом деле нет другого способа доступа к объекту. Он живет «там» в облаке динамичности, это не сидит в локальном масштабе.

Из-за этого использование указателей иногда путают с использованием динамического хранилища, но на самом деле первое не имеет причинно-следственного отношения к последнему.

+0

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

+3

@ TamásSzelei: В этом случае вы должны использовать ссылку. –

+0

@ TamásSzelei: Ну, правда. Это очень локализованное использование. Я предполагаю, что я говорю о получении указателя и его хранении, а не использовании временного в одном выражении –

13

Объект, созданный таким образом:

int foo; 

имеет автоматическую продолжительность хранения - объект живет, пока переменная foo не выходит из области видимости. Это означает, что в вашем первом примере dynInt будет некорректным указателем, как только someInt выходит за рамки (например, в конце функции).

Объект, созданный таким образом:

int foo* = new int; 

Имеет динамическую длительность хранения - объект живет, пока вы явно не вызовете delete на нем.

Инициализация объектов является ортогональной концепцией; он напрямую не связан с тем типом хранения, который вы используете. См. here для получения дополнительной информации об инициализации.

+0

Спасибо за такой хороший и ясный ответ. Еще раз спасибо. – vinay

+2

Чтобы быть педантичным, вы действительно ничего не знаете о * памяти *, потому что это зависит от реализации вашей функции распределения ':: operator new()'. Это * время жизни * объекта, который является динамическим, т. Е. Руководство: * объект * живет до тех пор, пока вы его не удалите. C++ разделяет выделение памяти и построение объекта. –

+1

@KerrekSB: Я всегда немного напуган при написании такого ответа, потому что я уверен, что что-то не так :) –

1

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

+0

Если вы не используете [alloca] (http://stackoverflow.com/q/1018853/533120);) –

4

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

Однако, как правило, для динамического распределения используется более широкое использование. Есть много вещей, которые ваша программа не знает до распределения и зависит от ввода. Например, ваша программа должна прочитать файл изображения. Насколько велик этот файл изображения? Мы могли бы сказать, что мы хранить его в массиве, как это:

unsigned char data[1000000]; 

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

unsigned char* data = new unsigned char[file_size]; 

Здесь file_size определяется во время выполнения. Вы не могли сказать это значение во время компиляции.

+1

Не могли бы вы просто сделать: «unsigned char data [file_size];»? – user1066113

+0

Нет, ты не мог. Ну, некоторые компиляторы C могут принять его, но это все еще динамическое распределение, а не стандартное C (89), ни C++; см. [VLA] (http://en.wikipedia.org/wiki/Variable-length_array). ISO C99 поддерживает VLA. –

2

Всякий раз, когда вы используете new в памяти C++, выделяется malloc, который вызывает системный вызов sbrk (или аналогичный). Поэтому никто, кроме ОС, не знает о запрошенном размере. Поэтому вам нужно будет использовать delete (который вызывает free, который снова возвращается к sbrk) для возврата памяти в систему. В противном случае вы получите утечку памяти.

Теперь, когда дело доходит до вашего второго случая, у компилятора есть знания о размере выделенной памяти. То есть, в вашем случае, размер одного int. Установка указателя на адрес этого int ничего не меняет в знании необходимой памяти. Или другими словами: компилятор способен заботиться о освобождении памяти. В первом случае с new это невозможно.

В дополнение к этому: new, соответственно malloc, не нужно выделять точно запрошенный размер, что делает вещи немного сложнее.

Редактировать

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

2

Подробнее о dynamic memory allocation, а также garbage collection

Вам действительно нужно читать хорошую C или C++ программирование книги.

Объясняя подробно, потребуется много времени.

Куча - это память, внутри которой происходит динамическое распределение (с new в C++ или malloc в C). Есть system calls, связанные с ростом и сжатием кучи. В Linux это mmap & munmap (используется для реализации malloc и new и т. Д.).

Вы можете много раз вызывать примитив распределения. Таким образом, вы можете поставить int *p = new int; внутри цикла и получить новое место при каждом цикле!

Не забудьте освободить память (с delete в C++ или free в C). В противном случае вы получите memory leak - непослушный вид ошибки. В Linux valgrind помогает их поймать.

1

В двух словах, срок службы динамически выделенного объекта управляется вами, а не языком. Это позволяет вам жить до тех пор, пока это требуется (в отличие от конца области), возможно, определяется условием, которое может быть рассчитано только при запуске.

Кроме того, динамическая память, как правило, гораздо более «масштабируема», то есть вы можете выделять больше и/или более крупных объектов по сравнению с распределением на основе стека.

Распределение по существу «маркирует» кусок памяти, поэтому никакой другой объект не может быть выделен в одном пространстве. Де-распределение «отменяет» эту часть памяти, чтобы ее можно было повторно использовать для последующих распределений. Если вам не удастся освободить память после того, как она больше не нужна, вы получите условие, известное как «утечка памяти» - ваша программа занимает память, которая больше не нужна, что приводит к возможному сбою в распределении новой памяти (из-за отсутствия свободной память), и просто в целом ставит ненужную нагрузку на систему.

4
  1. Ваша программа получает начальный кусок памяти при запуске. Эта память называется стеком . В настоящее время сумма составляет около 2 МБ.

  2. Ваша программа может запросить ОС для дополнительной памяти. Это называется распределением динамической памяти. Это выделяет память на бесплатный магазин (терминология C++) или куча (терминология C). Вы можете запросить столько памяти, сколько система готова предоставить (несколько гигабайт).

Синтаксис для выделения переменной в стек выглядит следующим образом:

{ 
    int a; // allocate on the stack 
} // automatic cleanup on scope exit 

Синтаксис для выделения переменной с использованием памяти из свободного магазина выглядит следующим образом:

int * a = new int; // ask OS memory for storing an int 
delete a; // user is responsible for deleting the object 


Чтобы ответить на ваши вопросы:

Когда один метод предпочтительнее другого.

  1. В общем случае предпочтительным является распределение стеков.
  2. Динамическое распределение требуется, когда вам нужно сохранить полиморфный объект, используя его базовый тип.
  3. Всегда использовать смарт-указатель для автоматизации удаления:
    • C++ 03: boost::scoped_ptr, boost::shared_ptr или std::auto_ptr.
    • C++ 11: std::unique_ptr или std::shared_ptr.

Например:

// stack allocation (safe) 
Circle c; 

// heap allocation (unsafe) 
Shape * shape = new Circle; 
delete shape; 

// heap allocation with smart pointers (safe) 
std::unique_ptr<Shape> shape(new Circle); 

Более того, почему мне нужно, чтобы освободить память в первом случае, но не во втором случае.

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

+0

Этот ответ намного лучше, чем другие, потому что он фактически объясняет, какое распределение памяти использовать, в какой ситуации –

Смежные вопросы