Russian Belarusian English German Japanese Ukrainian
  • Главная
  • Динамическое распределение памяти
Динамическое распределение памяти

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

Для динамического распределения выделяется специальная область памяти heap. Динамическое распределение памяти в этой области может производиться несколькими способами: с помощью библиотечных функций malloc, calloc, realloc, free или с помощью операций new и delete.

Указанные функции объявлены в файле stdlih.h или alloc.h. Объявление функции malloc следующее:

void *malloc(size_t size);

Функция выделяет в heap блок размером в size байтов. В случае успешного выделения памяти функция возвращает указатель на выделенный блок. Если не хватило места для блока требуемого размера или если size = 0, возвращается NULL. Другая функция calloc объявлена следующим образом:

void *calloc(size_t nitems, size_t size);

Функция выделяет память под nitems объектов, размер каждого из которых равен size. Таким образом общий объем выделяемой памяти составляет nitems * size. Выделенная память инициализируется нулями. В случае успешного выделения памяти функция возвращает указатель на выделенный блок. Если не хватило места для блока требуемого размера или если size = 0 или nitems = 0, возвращается NULL.

Следующая функция realloc позволяет изменить размер ранее выделенного блока памяти. Функция объявлена следующим образом:

void *realloc(void *block, size_t size);

Она изменяет размер блока в heap, на который указывает block, до размера size. При этом предполагается, что block указывает блок памяти, выделенной ранее функциями malloc, calloc или realloc. Если же аргумент block задан равным NULL, то функция realloc работает так же, как описанная выше функция malloc.

Если размер size задан равным нулю, то выделенный ранее блок, на который указывает block, освобождается, а функция возвращает NULL. Таким образом, функция с size равным 0 может использоваться не для выделения памяти, а для освобождения памяти, выделенной ранее. Если блок нового размера не может быть выделен, то функция realloc возвращает NULL. Если же память выделилась успешно, то возвращается адрес выделенного блока. При этом он может отличаться от начального значения block, поскольку функция при необходимости осуществляет копирование содержимого блока в новое место.

Функция free объявлена следующим образом:

void free(void *block);

Она освобождает блок памяти, выделенный ранее функциями malloc, calloc или realloc, на который указывает block.

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

#include <stdio.h>
#include <alloc.h>
char *str;
// str - указатель на строку, под которую выделена память
str = (char *) malloc(100);
// освобождение памяти
free(str);

В этом примере можно было бы использовать для выделения памяти функцию calloc:

str = (char *) calloc(100, sizeof(char));

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

str = (char *) realloc(str, 20);

Впрочем, к тому же результату привел бы и более простой оператор:

realloc(str, 20);

Необходимо помнить, что рассмотренные функции возвращают NULL(0), если память не удалось выделить. Поэтому прежде, чем использовать возвращенные ими указатели, надо обязательно проверять, не равны ли они NULL. Иначе возможны очень тяжелые ошибки при работе программы.

Операция new работает аналогично функции malloc, но лучше использовать именно ее, а не malloc. Это пожелание становится безусловной необходимостью, если речь идет о динамическом размещении в памяти объектов библиотеки компонентов С++ Builder. Операция new имеет следующий синтаксис:

<::> new <размещение> тип <(инициализатор)>
<::> new <размещение> (тип) <(инициализатор)>

Операция возвращает указатель на динамически размещенный в памяти объект. Все элементы, заключенные в описании синтаксиса в угловые скобки, являются необязательными. Операция разрешения области действия (::) позволяет обратиться к глобальной версии new, если наряду с ней возможно использование перегруженных операций. Элемент размещение используется (если он предусмотрен перегруженной версией) для дополнительной информации о месте размещения в памяти. Инициализатор задает начальное значение создаваемого объекта. Таким образом, обязательно должен быть указан только тип данных. Например:

double *А = new double;

В данном случае в памяти динамически создается объект - действительное число. В дальнейшем доступ к нему осуществляется как *А. Например:

*А = 5.1;
Labell->Caption - *А;

Если нет желания вводить указатель на объект и в дальнейшем работать с этим указателем, можно динамически разместить объект с помощью следующего оператора:

double В = *new double;

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

double *А = new double(5.1);
double В = *new double(5.5);

Ниже приведен пример создания и динамического размещения в памяти компонента окна редактирования типа TEdit:

TEdit *Edit = new TEdit(this);
Edit->Parent = Form1;

Первый оператор выделяет память под объект и создает его, передавая в него указатель this как владельца Owner. Второй задает для компонента родителя Form1. В этот момент компонент станет виден на форме.

Операция определяет объем необходимой памяти, использую неявно операцию sizeof(тип). Если в динамически распределяемой области памяти есть место для размещения объекта, то выделяется соответствующий блок памяти и операция new возвращает указатель на объект данного типа. При этом нет необходимости явно приводить тип этого указателя все делается автоматически. Созданный объект хранится в памяти, пока не будет уничтожен описанной далее операцией delete или пока на завершится выполнение программы.

Если в памяти невозможно выделить блок требуемого размера, генерируется исключение bad_alloc. Поэтому в программе всегда надо предусматривать блок catch, который бы перехватывал это исключение прежде, чем программа попытается получить доступ к создаваемому объекту. Таким образом, динамическое размещение объектов в памяти, как правило, должно оформляться следующим образом:

#include <iostream.h>
try {
Операторы динамического распределения памяти с помощью new
}
catch(std::bad_alloc)
{
Операторы действий при недостаточной памяти
}

Можно отменить генерацию исключения bad_alloc, задавая указатель на свой собственный обработчик событий, связанных с невозможностью выделить память. Для этого используется оператор set new_handler(указатель) который позволяет задать указатель на обработчик. При этом set_new_handler возвращает прежний указатель, который был зарегистрирован до этого. Например, можно описать функцию:

void F1(void)
{
ShowMessage("Не хватает памяти");
exit(1);
}

которая обрабатывает ситуацию, связанную с нехваткой памяти, и ввести в программу (например, в обработчик события OnCreate формы) оператор:

set_new_handler(F1);

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

Можно отменить генерацию исключения bad_alloc, не вводя специального обработчика, а просто записав оператор:

set_new_handler(0);

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

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

double *А = new double[100];

динамически размещает массив из 100 действительных чисел. К его элементам в дальнейшем можно обращаться как обычно, по индексу: A[ind], При использовании для создания массива операции new надо иметь в виду, что в момент создания его нельзя инициализировать, как это делается с одиночными объектами. Можно создавать и многомерные массивы. Например, оператор:

double *M = new double[100][100];

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

double *M = new double[n][100];

Динамически распределенную память надо освобождать, когда отпадает необходимость в размещенных в ней объектах. В противном случае получится неоправданная утечка памяти. Освобождение памяти осуществляется операцией delete. Она выполняет то же, что описанная ранее стандартная библиотечная функция free. Но использование delete предпочтительнее. Во всяком случае все, что размещается в памяти операцией new, должно удаляться операцией delete. Операция может иметь следующие формы записи:

<::> delete <выражение>
<::> delete [ ] <выражение>
delete <имя_массива> [ ] ;

Например:

double *А = new double (5.1);
delete А;

или

double *А = new double [100];
delete [] A;

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

delete А;
А = NULL;

Если заметили ошибку, выделите фрагмент текста и нажмите Ctrl+Enter

Добавить комментарий


Поиск по сайту