Выбрать главу

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, '\0', amount);

Другой возможностью является использование calloc(), которая вскоре будет описана.

Джефф Колье (Geoff Collyer) рекомендует следующую методику для выделения памяти:

some_type *pointer;

pointer = malloc(count * sizeof(*pointer));

Этот подход гарантирует, что malloc() выделит правильное количество памяти без необходимости смотреть объявление pointer. Если тип pointer впоследствии изменится, оператор sizeof автоматически гарантирует, что выделяемое число байтов остается правильным. (Методика Джеффа опускает приведение типов, которое мы только что обсуждали. Наличие там приведения типов также гарантирует диагностику, если тип pointer изменится, а вызов malloc() не будет обновлен.)

3.2.1.3. Освобождение памяти: free()

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

free(coordinates);

coordinates = NULL; /* не требуется, но хорошая мысль */

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

Доступ к освобожденной памяти

Если она не была освобождена, переменная coordinates продолжает указывать на блок памяти, который больше не принадлежит приложению. Это называется зависшим указателем (dangling pointer). На многих системах вы можете уйти от наказания, продолжая использовать эту память, по крайней мере до следующего выделения или освобождения памяти. На других системах, однако, такой доступ не будет работать. В общем, доступ к освобожденной памяти является плохой мыслью: это непереносимо и ненадежно, и GNU Coding Standards отвергает его. По этой причине неплохо сразу же установить в указателе программы значение NULL. Если затем вы случайно попытаетесь получить доступ к освобожденной памяти, программа немедленно завершится с ошибкой нарушения сегментации (надеемся, до того, как вы успели вашу программу выпустить в свет).

Освобождение одного и того же указателя дважды

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

Передача указателя, полученного не от функций malloc(), calloc() или realloc()

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

free(coordinates + 10);

/* Освободить все кроме первых 10 элементов */

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