Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЗФ_ОАиП / Лекции ГГУ Скорины - Программирование.doc
Скачиваний:
179
Добавлен:
21.03.2016
Размер:
2.27 Mб
Скачать

30. Динамическая память

30.1. Понятие динамического объекта

Данные, которые создаются и уничтожаются по требованию программиста, называются динамическими.

Чтобы выделить память под обычную переменную, ее надо описать: int a;. Обратиться к такой переменной можно или по имени (a=10;), или через указатель, в который занесен адрес переменной (int *pa=&a; *pa=20;). Количество таких переменных фиксировано – ровно столько, сколько их определено в тексте программы.

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

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

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

30.2 Создание и уничтожение динамических объектов

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

Работа функций динамического распределения памяти различается для различных моделей памяти, поддерживаемых системой программирования. Происходит это из-за разных способов организации и размеров куч в малых моделях памяти (tiny, small, medium) и в больших моделях памяти (compact, large, huge).

Прототипы функций для работы с динамической памятью содержатся в файле <alloc.h>, а также в файле <stdlib.h>.

Функции malloc() и calloc() служат для динамического запроса блоков свободной памяти, т.е. для создания динамических объектов. Эти функции возвращают адрес созданного динамического объекта.

void *malloc(unsigned int size);

Выделить память объемом size байт. Функция возвращает указатель на выделенную память или NULL, если запрос удовлетворить нельзя. Значение NULL возвращается и в том случае, когда значение параметра size нулевое. Выделенная память никак не инициализируется (содержит «мусор» – случайные значения).

void *calloc(unsigned int n, unsigned int size);

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

Обратить внимание! В модели Large (установлена по умолчанию в классе 1-1) параметры функций имеют тип unsigned long.

Для определения необходимого объема памяти желательно использовать оператор sizeof.

Функции возвращают указатель типа void *, поэтому при вызове функций в программе необходимо применять операцию приведения к соответствующему типу:

char *ps;

int *pi;

ps = (char *)malloc(100);

if (ps == NULL) return;

pi = (int *)calloc(1, sizeof(int));

if (pi == NULL) return;

Обратить внимание! Всегда надо проверять в программе, успешно ли выделилась память.

Функция realloc() служит для изменения размера ранее выделенного блока памяти, адрес которого содержится в указателе block. Параметр size задает новый размер блока. Функция гарантирует сохранность старых данных в блоке (понятно, что сохранность не более size байтов).

void *realloc (void *block, unsigned int size);

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

char *ps;

// захватывает блок памяти для 50 символов ps = malloc(50 * sizeof(char));

if (ps == NULL) return;

// перезахватывает блок для 100 символов

ps = realloc(ps, 100 * sizeof(char));

if (ps == NULL) return;

Обратить внимание! Все рассмотренные функции могут выделять память размером не более одного сегмента, то есть не более 64K в 16-ти разрядных моделях и не более 4G в 32-х разрядных моделях памяти. При работе с динамической памятью следует иметь в виду, что в каждом выделенном блоке несколько байт отводится на служебную информацию. Поэтому последствия затирания динамической памяти могут быть существенными.

К динамическим объектам нельзя обратиться по имени, так как они просто не имеют имени. У нас есть только указатель с адресом динамического объекта. Поэтому для доступа к самому динамическому объекту используется операция разименования *:

int *pi;

pi = (int *)calloc(1, sizeof(int));

if (pi == NULL) return;

*pi = 55;

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

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

В языке С нет так называемого «сборщика мусора» (есть, например, в Java), осуществляющего поиск объектов, на которые отсутствуют ссылки, и делающего эту память доступной для повторного использования. Вот поэтому очень желательно всю захваченную в программе память освобождать явно, несмотря даже на то, что она неявно автоматически освобождается по концу работы программы. Бывают программы, которые работают длительно (так называемый режим работы «24/7»). Конечно, доступной для выделения памяти в современных компьютерах много, но все-таки ее не бесконечно много. И когда-нибудь она может и закончиться. Еще одна причина для явного освобождения памяти в программе – так называемая «утечка» памяти, когда происходит потеря памяти, т.е. становится невозможным ее повторное использование (например, в результате потери адреса выделенной области).

Для явного освобождения памяти используется функция free().

void free(void *block);

Функция освобождает область памяти, на которую указывает block. В указателе должен содержаться адрес области памяти, полученной с помощью malloc(), calloc() или realloc(). Нельзя освобождать области, которые не были получены с помощью этих функций. Нельзя также повторно освобождать те области памяти, которые уже освобождены. Вызов free(p) для p=NULL не вызывает никаких действий со стороны функции free().

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

fpee(p);

p = p->next; // ошибка!!!

Задача. Разработать функцию для нахождения минимума из двух целых чисел и вызвать ее из функции main(). Все переменные в программе должны быть переменными-указателями. Формальные параметры функции могут быть обычными переменными.

void fmin(int a, int b, int *min) {

*min = a;

if (b < *min)

*min = b;

}

void main() {

int *a, *b, *m; // все переменные – только указатели

a = (int *)malloc(sizeof(int)); //выделение памяти под a

b = (int *)malloc(sizeof(int)); //выделение памяти под b

m = (int *)malloc(sizeof(int)); //выделение памяти под c

printf("Введите два числа: ");

scanf("%d %d", a, b);

printf("a=%d b=%d\n", *a, *b);

fmin(*a, *b, m);

printf("min=%d\n", *m);

free(a); free(b); free(m);

}