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

утечка мозга

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

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


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

Одним их самых мерзких недостатков языка Си является отсутствие поддержки динамических стековых массивов. Стековая память хороша тем, что автоматически освобождается при выходе из функции, однако, выделение стековых массивов «на лету» невозможно, и мы должны заранее объявить их размеры при объявлении переменных. В C++ сделаны небольшие подвижки в этом направлении, и теперь мы можем объявлять массивы, размер которых задается аргументом, передаваемым функции, однако, это не решает всех проблем и к тому же C++ поддерживают далеко не все компиляторы.

В частности, компилятор GCC 2.95 нормально «переваривает» следующий код, а Microsoft Visual C++, увы, нет:

f(int n)

{

char buf[n];

return sizeof(buf);

}

На самом деле, выделять динамические массивы все-таки возможно, но только в том случае если компилятор, во-первых, адресует локальные переменные через EBP, а во-вторых, в эпилоге использует конструкцию MOV ESP, EBP вместо ADD ESP, n. К таким компиляторам, в частности, относится Microsoft Visual C++, автоматически переходящий на адресацию локальных переменных через регистр EBP, если в теле функции присутствует хотя бы одна ассемблерная вставка.

Фрагмент одной из таких функций приведен ниже (компилировано Microsoft Visual C++ с максимальной оптимизацией (ключ /Ox):

text:00000010 push ebp

text:00000011 mov ebp, esp

text:00000013 push esi

text:0000002B sub esp, 400h

text:00000034 mov eax, [ebp+var_4]

text:00000048 pop esi

text:00000049 mov esp, ebp

text:0000004B pop ebp

text:0000004C retn

Выделение памяти на стеке осуществляется путем «приподнимания» регистра-указателя стека на некоторую величину, что можно сделать командой «SUB ESP, n», где n – количество выделяемых байт. Поскольку, компилятор адресует локальные переменные через регистр EBP, то изменение ESP не нарушит работы функции и все будет ОК, но... так будет продолжаться недолго. При выходе из функции она попытается восстановить регистры, сохраненные на входе (в данном случае — это регистр ESI), но на вершине перемещенного стека их не окажется! В регистр ESI попадет мусор, и материнская функция рухнет.

Существует, по меньшей мере, два решения проблемы: либо вручную опускаем регистр ESP при выходе из функции (если, конечно, не забудем это сделать), либо копируем на вершину выделенного блока порядка 20h байт памяти с макушки старого стека (обычно, этого более чем достаточно: даже если функция сохраняет все регистры общего назначения, ей требуется всего лишь 1Сh байт). В этом случае о ручном освобождении выделенной памяти можно не заботиться. Это сделает машинная команда MOV ESP, EBP, помещенная компилятором в эпилог функции.

Ниже приведена пара макросов для динамического выделения освобождения стековой памяти (только для Microsoft Visual C++):

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