Динамическое распределение памяти в языке С
Теоретическая часть
Динамическая память – это оперативная память компьютера, предоставляемая программе при ее работе. Динамическое размещение данных означает использование динамической памяти при работе программы. В отличие от этого статическое размещение (например, явное определение массива данных заданного типа) осуществляется компилятором в процессе компиляции (запуска) программы.
Указатели используются для динамического выделения памяти компьютера для хранения данных [9.1]. Динамическое распределение означает, что программа выделяет память для данных во время выполнения.
Память, выделяемая в С функциями динамического распределения данных, находится в так называемой динамически распределяемой области памяти (heap – куча) [9.1]. Динамически распределяемая область памяти – это свободная область памяти, не используемая программой, операционной системой или другими программами. Размер этой области памяти заранее неизвестен, но, как правило, в ней достаточно памяти для размещения данных программы. Хотя размер динамически распределяемой области памяти очень большой, все же она конечна и может быть исчерпана.
Основу системы динамического распределения памяти в С составляют библиотечные функции calloc(), malloc(), realloc() и free() [9.1].
Рассмотрим прототипы этих функций.
#include <stdlib.h> void *calloc(size_t num, size_t size);
Функция calloc() выделяет память, размер которой равен значению выражения num * size, т.е. память, достаточную для размещения массива, содержащего num объектов размером size [9.1]. Выделенная область памяти обнуляется. Функция calloc() возвращает указатель на первый байт выделенной области памяти для массива num объектов, каждый из которых имеет размер size или NULL, если запрос на память выполнить нельзя [9.2]. Если для удовлетворения запроса нет достаточного объема памяти, возвращается нулевой указатель. Перед попыткой использовать распределенную память важно проверить, что возвращаемое значение не равно нулю. Тип void может быть переопределен для требуемого типа, т.е. для char, int, float, double.
Пример фрагмента программного кода динамического распределения памяти для массивов заданного размера (например, вводится с клавиатуры):
double *ptr; ptr = (double *) (calloc(10, sizeof(double))); if (!ptr) // условие логического отрицания {printf("Out of memory\n"); exit(1);}
В приведенном примере число 10 – это размер одномерного массива с вещественными данными (типа double ). В случае выделения памяти для двухмерного массива размера N*M строчка с функцией calloc() перепишется так:
ptr = (double *) (calloc(N*M, sizeof(double)));
При этом двухмерный массив рассматривается как аналог одномерного массива размера N*M.
Использование явного приведения типов ( double ) сделано для того, чтобы обеспечить переносимость программы, в первую очередь для обеспечения совместимости с языком программирования С++.
#include <stdlib.h> void *malloc(size_t size);
Функция malloc() возвращает указатель на первый байт области памяти размера size, которая была выделена из динамически распределяемой области памяти [9.3]. Если для удовлетворения запроса в динамически распределяемой области памяти нет достаточного объема памяти, возвращается нулевой указатель NULL. При этом следует иметь в виду, что попытка использовать нулевой указатель обычно приводит к полному отказу системы. Выделенная область памяти не инициализируется [9.2].
Приведем фрагмент программного кода динамического распределения памяти для массивов заданного размера:
double *ptr; ptr = (double *) (malloc(10*sizeof(double))); if (!ptr) // условие логического отрицания { // выход за пределы памяти printf("Out of memory. Press any key: "); _getch(); exit(1); }
#include <stdlib.h> void *realloc(void *ptr, size_t size);
В стандарте С89 функция realloc() изменяет размер блока ранее выделенной памяти, адресуемой указателем *ptr в соответствии с заданным размером size [9.1]. Значение параметра size может быть больше или меньше, чем перераспределяемая область. Функция realloc() возвращает указатель на блок памяти, поскольку не исключена необходимость перемещения этого блока. В этом случае содержимое старого блока (до size байтов) копируется в новый блок. Если новый размер памяти больше старого, дополнительное пространство не инициализируется [9.2]. Если запрос невыполним, то функция распределения памяти realloc() возвращает нулевой указатель NULL. Функция realloc() позволяет перераспределить ранее выделенную память. При этом новый размер массива может быть как меньше предыдущего, так и больше его. Если система выделит память в новом месте, то все предыдущие значения, к которым программа обращалась по указателю *ptr, будут переписаны на новое место автоматически.
#include <stdlib.h> void free(void *ptr);
Функция free() возвращает в динамически распределяемую область памяти блок памяти, адресуемый указателем *ptr, после чего эта память становится доступной для выделения в будущем [9.1].
Вызов функции free() должен вызываться только с указателем, который был ранее получен в результате вызова одной из функций динамического распределения памяти. Использование недопустимого указателя при вызове, скорее всего, приведет к разрушению механизма управления памятью и, возможно, вызовет крах системы [9.1].
Практическая часть
Пример 1. Напишите программу считывания строк разной длины с использованием массива указателей, когда строки вводятся с клавиатуры, и вывода считанных строк на дисплей.
Программный код решения примера:
#include <stdio.h> #include <conio.h> #include <stdlib.h> #define N 79 int main (void) { int i, m = 3; char *str[N+1]; char *str2[] = {"st", "nd", "rd"}; for (i = 0; i < m; ++i) str[i] = (char *) calloc((N+1), sizeof(char)); printf("\n Dynamic reading strings of different lengths\n\n"); for (i = 0; i < m; ++i) { if (str[i] == NULL) { printf("\n\t Error memory allocation.\n"); printf("\n Press any key: "); _getch(); exit(1); } printf("\t Enter %d%s string: ", i+1, str2[i]); gets_s(str[i], sizeof(str)/sizeof(char)); } printf("\n\t The strings are:\n"); for (i = 0; i < m; ++i) printf("\t %s\n",str[i]); printf("\n\n Press any key: "); _getch(); return 0; }
Динамическое распределение памяти при каждом вводе новой строки осуществляется с помощью функции calloc(). Предусматривается проверка возвращаемого значения функции calloc(), которое не должно быть нулевым указателем, т.е. NULL. В функции gets_s() используется универсальное средство ( sizeof(str)/sizeof(char) ) определения размерности массива.
Возможный результат выполнения программы показан на рис. 9.1.