Freeing blocks from 1 to 999 in steps of 1
After free(), program break is: 0x8a13000
Если же будет высвобожден весь набор блоков в верхней части кучи, мы увидим, что крайняя точка программы снизится по сравнению со своим пиковым значением, показывая, что функция free() использовала системный вызов sbrk(), чтобы снизить положение крайней точки программы. Здесь высвобождаются последние 500 блоков выделенной памяти:
$ ./free_and_sbrk 1000 10240 1 500 1000
Initial program break: 0x804a6bc
Allocating 1000*10240 bytes
Program break is now: 0x8a13000
Freeing blocks from 500 to 1000 in steps of 1
After free(), program break is: 0x852b000
В этом случае функция free() (из библиотеки glibc) способна распознать, что высвобождается целая область на вершине кучи, поэтому при высвобождении блоков она объединяет соседние высвобождаемые блоки в один большой блок. (Такое объединение выполняется с целью избавления от большого количества мелких фрагментов в списке свободных блоков, каждый из которых может быть слишком мал для удовлетворения запросов последующих вызовов функции malloc().)
Функция free() из библиотеки glibc осуществляет вызов sbrk() для снижения уровня крайней точки программы, только когда высвобождаемый блок на вершине кучи «достаточно» большой. Здесь «достаточность» определяется параметрами, которые управляют операциями пакета функции из семейства malloc (обычно это 128 Кбайт). Тем самым снижается количество необходимых вызовов sbrk() (то есть количество системных вызовов brk()).
Использовать или не использовать функцию free()
Когда процесс завершается, вся его память возвращается системе, включая память кучи, выделенную функциями из семейства malloc. В программах, которые выделяют память и продолжают ее использовать до завершения своего выполнения, зачастую вызовы free() не применяются, поскольку они полагаются именно на это автоматическое высвобождение памяти. Такое поведение может принести особые преимущества в программах, выделяющих большое количество блоков памяти, поскольку добавление множества вызовов функции free() может дорого обойтись с точки зрения затрат времени центрального процессора, а также, возможно, усложнить сам код программы.
Хотя для многих программ вполне допустимо надеяться на автоматическое высвобождение памяти при завершении процесса, есть две причины, по которым желательно проводить явное высвобождение всей выделенной памяти.
• Явный вызов функции free() может повысить читаемость и упростить сопровождение программы при необходимости ее доработок.
• Если для поиска в программе утечек памяти используется отладочная библиотека пакета malloc (рассматриваемая ниже), то любая память, которая не была высвобождена явным образом, будет показана как утечка памяти. Это может усложнить задачу выявления реальных утечек памяти.
7.1.3. Реализация функций malloc() и free()
Хотя функциями malloc() и free() предоставляется интерфейс выделения памяти, который гораздо легче использовать, чем результат работы функций brk() и sbrk(), все же при его применении можно допустить ряд ошибок программирования. Разобраться в глубинных причинах таких ошибок и в способах их обхода поможет понимание внутреннего устройства функций malloc() и free().
Реализация функции malloc() достаточно проста. Сначала она сканирует список ранее высвобожденных функцией free() блоков памяти, чтобы найти тот блок, размер которого больше или равен предъявленным требованиям. (В зависимости от конкретной реализации для этого сканирования могут применяться различные стратегии, например
Если в списке не найдется ни одного достаточно большого блока, функция malloc() вызывает sbrk() для выделения большего количества памяти. Чтобы уменьшить количество вызовов sbrk(), вместо выделения именно того количества байтов, которое требуется, функция malloc() повышает уровень крайней точки программы, добавляя большее количество блоков памяти (сразу несколько единиц, соответствующих размеру виртуальной страницы памяти) и помещая избыточную память в список свободных блоков.
Если посмотреть на реализацию функции free(), то там все организовано еще интереснее. Как free(), когда она помещает блок памяти в список свободных блоков, узнает, какого размера этот блок? Это делается благодаря особому приему. Когда функция malloc() выделяет блок, она выделяет дополнительные байты для хранения целочисленного значения, содержащего размер блока. Это значение находится в начале блока. Адрес, возвращаемый вызывавшему функцию коду, указывает на то место, которое следует сразу же за значением длины (рис. 7.1).