Опубликован: 02.02.2011 | Уровень: для всех | Доступ: свободно
Лекция 25:

Распределение памяти. Динамическое выделение памяти

< Лекция 24 || Лекция 25: 123 || Лекция 26 >
Аннотация: В лекции рассматриваются определения, распределение, способы выделения и освобождения динамической памяти, взаимодействие указателей и участков динамической памяти.

Цель лекции: изучить способы выделения памяти, динамического выделения памяти, связи указателей и динамического распределения памяти, научиться решать задачи с использованием динамического выделения памяти в языке C++.

Существует два основных способа хранения информации в оперативной памяти. Первый заключается в использовании глобальных и локальных переменных. В случае глобальных переменных выделяемые под них поля памяти остаются неизменными на все время выполнения программы. Под локальные переменные программа отводит память из стекового пространства. Однако локальные переменные требуют предварительного определения объема памяти, выделяемой для каждой ситуации. Хотя С++ эффективно реализует такие переменные, они требуют от программиста заранее знать, какое количество памяти необходимо для каждой ситуации.

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

Распределение оперативной памяти для программ на С++

Рис. 24.1. Распределение оперативной памяти для программ на С++

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

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

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

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

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

Все переменные, объявленные в программе размещаются в одной непрерывной области памяти, которую называют сегментом данных. Такие переменные не меняют своего размера в ходе выполнения программы и называются статическими. Размера сегмента данных может быть недостаточно для размещения больших объемов информации. Выходом из этой ситуации является использование динамической памяти. Динамическая память – это память, выделяемая программе для ее работы за вычетом сегмента данных, стека, в котором размещаются локальные переменные подпрограмм и собственно тела программы.

Для работы с динамической памятью используют указатели. С их помощью осуществляется доступ к участкам динамической памяти, которые называются динамическими переменными. Для хранения динамических переменных выделяется специальная область памяти, называемая "кучей".

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

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

  1. использование операций new и delete ;
  2. использование семейства функций mallос ( calloc ) (унаследовано из С).

Работа с динамической памятью с помощью операций new и delete

В языке программирования С++ для динамического распределения памяти существуют операции new и delete. Эти операции используются для выделения и освобождения блоков памяти. Область памяти, в которой размещаются эти блоки, называется свободной памятью.

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

Синтаксис:

new ИмяТипа;

или

new ИмяТипа [Инициализатор];

В выделенный участок заносится значение, определяемое инициализатором, который не является обязательным элементом. В случае успешного выполнения new возвращает адрес начала выделенного участка памяти. Если участок нужных размеров не может быть выделен (нет памяти), то операция new возвращает нулевое значение адреса ( NULL ).

Синтаксис применения операции:

Указатель = new ИмяТипа [Инициализатор];

Операция new float выделяет участок памяти размером 4 байта. Операция new int(15) выделяет участок памяти 4 байта и инициализирует этот участок целым значением 15. Синтаксис использования операций new и delete предполагает применение указателей. Предварительно каждый указатель должен быть объявлен:

тип *ИмяУказателя;

Например:

float *pi;   //Объявление переменной pi
pi=new float; //Выделение памяти для переменной pi
* pi = 2.25; //Присваивание значения

В качестве типа можно использовать, например, стандартные типы int, long, float, double, char.

Оператор new чаще всего используется для размещения в памяти данных определенных пользователем типов, например, структур:

struct Node {
             char *Name;
             int Value;
             Node *Next
            };

Node *PNode; //объявляется указатель

PNode = new Node; //выделяется память

PNode->Name = "Ata"; //присваиваются значения
PNode->Value = 1;
PNode->Next = NULL;

В качестве имени типа в операции new может быть использован массив:

new ТипМассива

При выделении динамической памяти для массива его размеры должны быть полностью определены. Например:

ptr = new int [10];//10 элементов типа int или 40 байт
ptr = new int [ ];//неверно, т.к. не определен размер

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

int *n = new int;

Операция new выполняет выделение достаточного для размещения величины типа int участка динамической памяти и записывает адрес начала этого участка в переменную n. Память под саму переменную n (размера, достаточного для размещения указателя) выделяется на этапе компиляции.

int *b = new int (10);

В данном операторе, кроме описанных выше действий, производится инициализация выделенной динамической памяти значением 10.

int *q = new int [10];

В этом случае операция new выполняет выделение памяти под 10 величин типа int (массива из 10 элементов) и записывает адрес начала этого участка в переменную q, которая может трактоваться как имя массива. Через имя можно обращаться к любому элементу массива.

Есть ряд преимуществ использования new. Во-первых, операция new автоматически вычисляет размер необходимой памяти. Нет необходимости в использовании операции sizeof(). Более важно то, что она предотвращает случайное выделение неправильного количества памяти. Во-вторых, операция new автоматически возвращает указатель требуемого типа.

Для освобождения выделенного операцией new участка памяти используется операции:

delete указатель;

Указатель адресует освобождаемый участок памяти, ранее выделенный с помощью операции new. Например:

delete x;

Для освобождения памяти, выделенной для массива, используется следующая модификация той же операции:

delete [ ] указатель;

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

Пример 1. Демонстрация выполнения операций с динамической памятью.

#include "stdafx.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
  int *pa, *pb; 
  pa = new int; 
  *pa = 21; 
  pb = pa; 
  cout << *pa << "  " << *pb << "\n";
  pb = new int; 
  *pb = 28; 
  cout << *pa << "  " << *pb << "\n";
  delete pa;
  pa = pb; 
  cout << *pa << "  " << *pb << "\n";
  delete pa; 
  system("pause");
  return 0;
}

Результат выполнения программы:

21  21
21  28
28  28
< Лекция 24 || Лекция 25: 123 || Лекция 26 >
Денис Курбатов
Денис Курбатов
Владислав Нагорный
Владислав Нагорный

Подскажите, пожалуйста, планируете ли вы возобновление программ высшего образования? Если да, есть ли какие-то примерные сроки?

Спасибо!