Тип
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 «Определение ошибок».) Например, предположим, что мы хотим выделить переменное число некоторых структур. Код выглядит примерно так:
struct coord { /* 3D координаты */
int x, y, z;
} *coordinates;
unsigned int count; /* сколько нам нужно */
size_t amount; /* общий размер памяти */
/* ... как-нибудь определить нужное число... */
amount = count * sizeof(struct coord); /* сколько байт выделить */
coordinates = (struct coord*)malloc(amount); /* выделить память */
if (coordinates == NULL) {
/* сообщить об ошибке, восстановить или прервать */
}
/* ... использовать координаты... */
Представленные здесь шаги являются стереотипными. Порядок следующий:
1. Объявить указатель соответствующего типа для выделенной памяти.
2. Вычислить размер выделяемой памяти в байтах. Для этого нужно умножить число нужных объектов на размер каждого из них. Последний получается с помощью оператора С
sizeof
, который для этой цели и существует (наряду с другими). Таким образом, хотя размер определенной структуры среди различных компиляторов и архитектур может различаться,
sizeof
всегда возвращает верное значение, а исходный код остается правильным и переносимым.
При выделении массивов для строк символов или других данных типа
char
нет необходимости умножения на
sizeof(char)
, поскольку последнее по определению всегда равно 1. Но в любом случае это не повредит.
3. Выделить память с помощью
malloc()
, присвоив возвращаемое функцией значение переменной указателя. Хорошей практикой является приведение возвращаемого
malloc()
значения к типу переменной, которой это значение присваивается. В С этого не требуется (хотя компилятор может выдать предупреждение). Мы настоятельно рекомендуем всегда приводить возвращаемое значение.
Обратите внимание, что на C++ присвоение знамения указателя одного типа указателю другого типа требует приведения типов, какой бы ни был контекст. Для управления динамической памятью программы C++ должны использовать
new
и
delete
, а не
malloc()
и
free()
, чтобы избежать проблем с типами.
4. Проверить возвращенное значение. Никогда не предполагайте, что выделение памяти было успешным. Если выделение памяти завершилось неудачей,
malloc()
возвращает
NULL
. Если вы используете значение без проверки, ваша программа может быть немедленно завершена из-за
нарушения сегментации (segmentation violation), которое является попыткой использования памяти за пределами своего адресного пространства.
Если вы проверите возвращенное значение, вы можете по крайней мере выдать диагностическое сообщение и корректно завершить программу. Или можете попытаться использовать какой-нибудь другой способ восстановления.
Выделив блок памяти и установив в
coordinates
указатель на него, мы можем затем интерпретировать
coordinates
как массив, хотя он в действительности указатель:
int cur_x, cur_y, cur_z;
size_t an_index;
an_index = something;
cur_x = coordinates[an_index].x;
cur_y = coordinates[an_index].y;
cur_z = coordinates[an_index].z;
Компилятор создает корректный код для индексирования через указатель при получении доступа к членам структуры
coordinates[an_index]
.
ЗАМЕЧАНИЕ. Блок памяти, возвращенный
malloc()
, не инициализирован. Он может содержать любой случайный мусор. Необходимо сразу же инициализировать память нужными значениями или хотя бы нулями. В последнем случае используйте функцию
memset()
(которая обсуждается в разделе 12.2 «Низкоуровневая память, функции
memXXX()
):
memset(coordinates, ' ', amount);
Другой возможностью является использование
calloc()
, которая вскоре будет описана.
Джефф Колье (Geoff Collyer) рекомендует следующую методику для выделения памяти:
some_type *pointer;
pointer = malloc(count * sizeof(*pointer));
Этот подход гарантирует, что
malloc()
выделит правильное количество памяти без необходимости смотреть объявление pointer. Если тип
pointer
впоследствии изменится, оператор
sizeof
автоматически гарантирует, что выделяемое число байтов остается правильным. (Методика Джеффа опускает приведение типов, которое мы только что обсуждали. Наличие там приведения типов также гарантирует диагностику, если тип
pointer
изменится, а вызов
malloc()
не будет обновлен.)