Четыре библиотечные функции образуют основу управления динамической памятью С Мы опишем сначала их, затем последуют описания двух системных вызовов, поверх которых построены эти библиотечные функции. Библиотечные функции С, в свою очередь, обычно используются для реализации других выделяющих память библиотечных функций и операторов C++
new
и
delete
.
Наконец, мы обсудим функцию, которую часто используют, но которую мы не рекомендуем использовать.
3.2.1. Библиотечные вызовы:
malloc
,
calloc
,
realloc
,
free
Динамическую
память выделяют с помощью функций
malloc
или
calloc
. Эти функции возвращают указатели на выделенную память. Когда у вас есть блок памяти определенного первоначального размера, вы можете изменить его размер с помощью функции
realloc
. Динамическая память освобождается функцией
free
.
Отладка использования динамической памяти сама по себе является важной темой. Инструменты для этой цели мы обсудим в разделе 15.5.2 «Отладчики выделения памяти».
3.2.1.1. Исследование подробностей на языке С
Вот объявления функций из темы справки GNU/Linux malloc(3):
#include <stdlib.h> /* ISO С */
void *calloc(size_t nmemb, size_t size);
/* Выделить и инициализировать нулями */
void *malloc(size_t size);
/* Выделить без инициализации */
void free(void *ptr);
/* Освободить память */
void *realloc(void *ptr, size_t size);
/* Изменить размер выделенной памяти */
Функции выделения памяти возвращают тип
void*
. Это бестиповый или общий указатель, все, что с ним можно делать — это привести его к другому типу и назначить типизированному указателю. Примеры впереди.
Тип
size_t
является беззнаковым целым типом, который представляет размер памяти. Он используется для динамического выделения памяти, и далее в книге мы увидим множество примеров его использования. На большинстве современных систем
size
_t является
unsigned long
, но лучше явно использовать
size_t
вместо простого целого типа
unsigned
.
Тип
ptrdiff_t
используется для вычисления адреса в арифметике указателей, как в случае вычисления указателя в массиве:
#define MAXBUF ...
char *p;
char buf[MAXBUF];
ptrdiff_t where;
p = buf;
while (/* некоторое условие */) {
...
p += something;
...
where = p - buf; /* какой у нас индекс? */
}
Заголовочный файл
<stdlib.h>
объявляет множество стандартных библиотечных
функций С и типов (таких, как
size_t
), он определяет также константу препроцессора
NULL
, которая представляет «нуль» или недействительный указатель. (Это нулевое значение, такое, как 0 или '
((void*)0)
'. Явное использование 0 относится к стилю С++; в С, однако,
NULL
является предпочтительным, мы находим его гораздо более читабельным для кода С.)
3.2.1.2. Начальное выделение памяти:
malloc
Сначала память выделяется с помощью
malloc
. Передаваемое функции значение является общим числом затребованных байтов. Возвращаемое значение является указателем на вновь выделенную область памяти или
NULL
, если память выделить невозможно. В последнем случае для обозначения ошибки будет установлен
errno
. (errno является специальной переменной, которую системные вызовы и библиотечные функции устанавливают для указания произошедшей ошибки. Она описывается в разделе 4.3 «Определение ошибок».) Например, предположим, что мы хотим выделить переменное число некоторых структур. Код выглядит примерно так:
coordinates = (struct coord*)malloc(amount); /* выделить память */
if (coordinates == NULL) {
/* сообщить об ошибке, восстановить или прервать */
}
/* ... использовать координаты... */
Представленные здесь шаги являются стереотипными. Порядок следующий:
1. Объявить указатель соответствующего типа для выделенной памяти.
2. Вычислить размер выделяемой памяти в байтах. Для этого нужно умножить число нужных объектов на размер каждого из них. Последний получается с помощью оператора С
sizeof
, который для этой цели и существует (наряду с другими). Таким образом, хотя размер определенной структуры среди различных компиляторов и архитектур может различаться,
sizeof
всегда возвращает верное значение, а исходный код остается правильным и переносимым.
При выделении массивов для строк символов или других данных типа
char
нет необходимости умножения на
sizeof(char)
, поскольку последнее по определению всегда равно 1. Но в любом случае это не повредит.
3. Выделить память с помощью
malloc
, присвоив возвращаемое функцией значение переменной указателя. Хорошей практикой является приведение возвращаемого
malloc
значения к типу переменной, которой это значение присваивается. В С этого не требуется (хотя компилятор может выдать предупреждение). Мы настоятельно рекомендуем всегда приводить возвращаемое значение.