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

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() пытается использовать эту информацию, она обнаружит там недействительные данные. В других реализациях, где учетная информация хранится в конце выделенного блока; возникают те же проблемы.)

Выход за пределы буфера

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

Отказ в освобождении памяти

Любая динамическая память, которая больше не нужна, должна быть освобождена. В частности, необходимо тщательно управлять памятью и освобождать ее, когда она выделяется внутри циклов или рекурсивных или глубоко вложенных вызовов функций. Отказ от этого ведет к утечкам памяти, при которых память процесса может неограниченно расти; в конце концов, процесс завершается из-за нехватки памяти. Эта ситуация может быть особенно разрушительной, если память выделяется для ввода записи или как-то еще связана с вводом: утечка памяти будет незаметна при использовании незначительных объемов ввода, но внезапно станет очевидной (и приведет в замешательство) при больших. Эта ошибка еще хуже для систем, которые должны работать непрерывно, как в системах телефонных коммутаторов. Утечка памяти, вызывающая крушение такой системы, может привести к значительным денежным или другим потерям.

Даже если программа никогда не завершается из-за недостатка памяти, постоянно увеличивающиеся программы теряют производительность, поскольку операционная система должна сохранять использующиеся данные в физической памяти. В худшем случае, это может привести к поведению, известному как пробуксовка (thrashing), при которой операционная система так занята перекачкой содержимого адресного пространства в и из физической памяти, что реальная работа не делается.

Хотя free() может вернуть освобожденную память системе и сократить адресное пространство процесса, это почти никогда не делается. Вместо этого освобожденная память готова для нового выделения при следующем вызове malloc(), calloc() или realloc().

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

Обсуждение ряда полезных инструментов для отладки динамической памяти см в разделе 15.5.2 «Отладчики выделения памяти».