Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #71, ОКТЯБРЬ 2006 г.

утечка мозга

КРИС КАСПЕРСКИ АКА МЫЩЪХ

Спецвыпуск: Хакер, номер #071, стр. 071-020-4


[утечки ресурсов]

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

Рассмотрим следующий пример:

foo()

{

FILE *ff

char *p1, *p2;

p1 = malloc(XXL);

ff = fopen(FN,"r");

if (bar()==ERROR) return -1;

p2 = malloc(XXL);

free(p1);

free(p2);

fclose(ff);

return 0;

}

Функция foo намеревается выделить два блока памяти p1 и p2, но реально успевает выделить лишь один из них, после чего bar завершается с ошибкой, делающей дальнейшее выполнение foo невозможным, - вот программист и совершает возврат по return, забывая о выделенном блоке памяти.

Проблема в том, что в произвольной точке программы очень непросто сказать, какие ресурсы уже выделены, а какие еще нет и что именно нужно освобождать! Ну ведь не поддерживать же ради этого транзакции?! Разумеется, нет. Проблема имеет весьма простое и элегантное решение, основанное на том, что Стандарт допускает освобождение нулевого указателя. Правда, к файлам и другим объектам это уже не относится, но проверку на ноль легко выполнить и вручную.

Правильно спроектированный код должен выглядеть приблизительно так:

foo()

{

int error = 0;

FILE *ff = 0;

char *p1 = 0; char *p2 = 0;

{

p1 = malloc(XXL);

ff = fopen(FN,"r");

if ((bar()==ERROR) && (error=-1)) break;

p2 = malloc(XXL);

} while(0);

free(p1); free(p2);

if (ff) fclose(ff);

return error;

}

Что изменилось? Абсолютно все! Теперь для внепланового выхода из программы (который осуществляется по break), нам уже не нужно помнить, что мы успели выделить или открыть! По завершении цикла while (который на самом деле никакой не цикл, а просто имитация критикуемого оператора goto), мы освобождаем (или, точнее, пытаемся освободить) все ресурсы, которые потенциально могли быть выделены. Структура программы значительно упрощается, и главное тут — не забыть освободить все, что мы выделили, но программисты об этом все равно забывают.

Решение заключается в создании своей собственной обертки вокруг функции malloc (условно назовем ее my_malloc), которая выделят память, запоминает указатель в своем списке/массиве и перед возвращением в вызываемую функцию подменяет адрес возврата из материнский функции на свой собственный обработчик (конечно, без ассемблерных вставок тут не обойтись, но они того стоят). Как следствие — при выходе из foo управление получает обработчик my_malloc, читающий список/массив и автоматически освобождающий все, что там есть, снимая тем самым эту ношу с плеч программиста.

Назад на стр. 071-020-3  Содержание  Вперед на стр. 071-020-5