Программа malloc-use, приведенная в листинге А.2, позволяет тестировать операции выделения, освобождения и обращения к памяти. Единственный аргумент командной строки задает максимальное число выделяемых буферов. Например, по команде malloc-use 12 будет создан массив А из двенадцати пустых указателей. Программа принимает пять разных команд.
■ Если ввести a i b, для элемента массива А[i] будет выделено b байтов. Индекс i должен быть неотрицательным числом, меньшим, чем аргумент командной строки. Число байтов также должно быть неотрицательным.
■ Если ввести d i, будет удален буфер A[i].
■ Если ввести r i p, из буфера A[i] будет прочитан p-й символ (A[i][p]). Значение p должно быть целым.
■ Если ввести w i p, в позицию p буфера A[i] будет записан символ.
■ Для завершения работы программы введите q.
Прежде чем привести исходный текст программы, опишем, как работать с ней.
А.2.2. Проверка функции malloc()
Функции выделения и освобождения памяти, имеющиеся в GNU-библиотеке языка С, способны обнаруживать факт записи в память до начала выделенной области, а также попытку освободить одну и ту же область дважды. Если задать переменную среды MALLOC_CHECK_ равной 2, программа malloc-use аварийно завершит работу в случае выявления такого рода ошибки. Подобное изменение поведения не требует перекомпиляции программы.
Вот что произойдет, если записать символ перед началом массива;
% export MALLOC_CHECK_=2
% ./malloc-use 12
Please enter a command: a 0 10
Please enter a command: w 0 -1
Please enter a command: d 0
Aborted (core dumped)
Команда export включила проверку функции malloc(), а значение 2 заставило программу завершиться сразу после обнаружения ошибки.
Проверка функции malloc() очень полезна, потому что программу не нужно перекомпилировать, однако возможности этой проверки весьма ограничены. В основном определяется, не были ли повреждены выделенные структуры данных. Таким образом, сразу же обнаруживаются попытки повторно удалить ту же самую область. Кроме того, выявляется факт записи данных непосредственно перед началом выделенного блока, поскольку его размер хранится именно там. К сожалению, проверка выполняется только тогда, когда программа вызывает функцию malloc() или free(), а не когда происходит обращение к памяти. То есть до обнаружения ошибки может произойти множество неправильных операций чтения и записи. В частности, в предыдущем примере ошибка записи была выявлена лишь при попытке освободить выделенную область.
А.2.3. Поиск потерянных блоков памяти с помощью утилиты mtrace
Утилита mtrace позволяет выявить наиболее распространенную ошибку при работе с динамической памятью: несоответствие числа операций выделения и освобождения памяти. Алгоритм применения утилиты таков.
1. Включите в программу файл <mcheck.h> и разместите в самом начале программы вызов функции mtrace(). Эта функция активизирует трассировку операций выделения и освобождения памяти.
2. Задайте имя файла, в котором будет сохраняться трассировочная информация. Это делается следующим образом:
% export MALLOC_TRACE=memory.log
3. Запустите программу. Все операции выделения и освобождения памяти будут зарегистрированы в журнальном файле.
4. Вызовите утилиту mtrace, которая проверит, совпадает ли число выделенных блоков памяти с числом освобожденных блоков.
% mtrace my_program $MALLOC_TRACE
Сообщения, выдаваемые утилитой mtrace, достаточно понятны. Например, в случае программы malloc-use будет получена такая информация:
- 0000000000 Free 3 was never alloc'd malloc-use.с:39
Memory not freed:
-----------------
Address Size Caller
0x08049d48 0xc at malloc-use.с:30
Эти сообщения говорят о том, что в строке 39 файла malloc-use.c делается попытка освободить память, которая никогда не была выделена, а память, выделенная в строке 30, так и не была освобождена.
Функция malloc() заставляет программу фиксировать все операции выделения и освобождения памяти в файле, указанном в переменной среды MALLOC_TRACE. Чтобы данные были записаны в файл, программа должна завершиться нормальным образом. Утилита mtrace анализирует этот файл и находит в нем непарные записи.