18.11.2014 Views

JIT SPRAY АНАЛИЗ TDSS - Xakep Online

JIT SPRAY АНАЛИЗ TDSS - Xakep Online

JIT SPRAY АНАЛИЗ TDSS - Xakep Online

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Особо чувствительных может смутить выделение одного байта памяти<br />

тогда, когда у нас запрашивают ноль. Да, это грубо, но зато работает.<br />

Дело в том, что мы должны вернуть корректный указатель даже тогда,<br />

когда у нас просят 0 байтов, поэтому приходится изобретать. И чем<br />

проще будет изобретение, тем оно надежней.<br />

Также сомнительной может показаться установка указателя на обработчик<br />

new в нулевое значение с последующим его восстановлением.<br />

К сожалению, у нас нет другого способа получить адрес текущей<br />

функции-обработчика. Нам нужно проверить этот адрес, и, если он<br />

нулевой, возбудить исключение типа bad_alloc.<br />

Еще раз повторюсь, что оператор new в случае проблем с выделением<br />

памяти в бесконечном цикле вызывает функцию-обработчик. Очень<br />

важно, чтобы код этой функции корректно разрешал проблему: сделал<br />

доступной памяти больше, возбудил исключение типа, производного<br />

от bad_alloc, установил другой обработчик, убрал текущий обработчик<br />

или не возвращал управление вовсе. В противном случае программа,<br />

вызвавшая нашу версию new, зависнет.<br />

Отдельно следует рассмотреть случай, когда new является функциейчленом<br />

какого-либо класса. Обычно пользовательские версии<br />

операторов работы с памятью пишутся для более оптимизированного<br />

распределения памяти. Например, new для класса Base заточен под<br />

выделение памяти объемом sizeof(Base) — ни больше, ни меньше.<br />

Но что будет, если мы создадим класс, который наследуется от Base?<br />

В дочернем классе также будет использоваться версия оператора<br />

new, определенного в Base. Но размер наследуемого класса (назовем<br />

его Derived), скорее всего, будет отличаться от размера базового:<br />

sizeof(Derived) != sizeof(Base). Из-за этого вся польза от собственной<br />

реализации new может сойти на нет. О чем, кстати, многие забывают и<br />

испытывают потом нечеловеческие страдания.<br />

Проблема наследования оператора new<br />

class Base {<br />

public:<br />

static void *operator new (std::size_t size)<br />

throw(std::bad_alloc);<br />

...<br />

};<br />

// â ïîäêëàññå íå îáúÿâëåí îïåðàòîð new<br />

class Derived: public Base<br />

{...};<br />

//âûçûâàåòñÿ Base::operator new<br />

Derived *p = new Derived;<br />

Решить проблему достаточно просто, но делать это надо заранее.<br />

Достаточно в базовом классе, в теле оператора new, выполнять проверку<br />

размера выделяемой памяти. Если количество запрашиваемых<br />

байтов не совпадает с количеством байтов в объекте класса Base, то<br />

работу по выделению памяти лучше всего передать стандартной реализации<br />

new. К тому же, так мы сразу же решаем вопрос с обработкой<br />

запроса на выделение нуля байтов памяти — этим уже будет заниматься<br />

штатная версия оператора.<br />

Решение проблемы наследования оператора new<br />

void *operator new (std::size_t size)<br />

throw(std::bad_alloc)<br />

{<br />

// åñëè size íåïðàâèëüíûé, âûçâàòü ñòàíäàðòíûé new<br />

XÀÊÅÐ 09 /140/ 10<br />

}<br />

if(size != sizeof(Base))<br />

return ::operator new(size);<br />

// â ïðîòèâíîì ñëó÷àå îáðàáîòàòü çàïðîñ<br />

...<br />

На уровне класса можно также определить new для массивов<br />

(operator new[]). Этот оператор не должен ничего делать, кроме как<br />

выделять блок неформатированной памяти. Мы не можем совершать<br />

какие-либо операции с еще не созданными объектами. Да и, к<br />

тому же, нам неизвестен размер этих объектов, ведь они могут быть<br />

наследниками класса, в котором определен new[]. То есть количество<br />

объектов в массиве необязательно равно (запрошенное число<br />

байтов)/sizeof(Base). Более того, для динамических массивов может<br />

выделяться большее количество памяти, чем займут сами объекты,<br />

для обеспечения резерва.<br />

Ñîãëàøåíèÿ ïðè<br />

íàïèñàíèè îïåðàòîðà delete<br />

Что касается оператора delete, то тут все гораздо проще. Основная<br />

гарантия, которую мы должны предоставить — это безопасность освобождения<br />

памяти по нулевому адресу. С учетом этого псевдокод delete<br />

будет выглядеть так:<br />

Псевдокод пользовательской<br />

реализации оператора delete<br />

void *operator delete (void *rawMemory) throw()<br />

{<br />

// åñëè íóëåâîé óêàçàòåëü, íè÷åãî íå äåëàòü<br />

if(rawMemory == 0) return;<br />

// îñâîáîäèòü ïàìÿòü, íà êîòîðóþ óêàçûâàåò<br />

rawMemory;<br />

}<br />

Если оператор delete является функцией-членом класса, то, как и в<br />

случае с new, следует позаботиться о проверке размера удаляемой<br />

памяти. Если пользовательская реализация new для класса Base<br />

выделила sizeof(Base) байтов памяти, то и самописный delete должен<br />

освободить ровно столько же байтов. В противном случае, если размер<br />

удаляемой памяти не совпадает с размером класса, в котором<br />

определен оператор, следует передать всю работу стандартному<br />

delete.<br />

Псевдокод функции-члена delete<br />

class Base {<br />

public:<br />

static void *operator new (std::size_t size)<br />

throw(std::bad_alloc);<br />

static void *operator delete<br />

(void *rawMemory, std::size_t size) throw();<br />

...<br />

};<br />

void* Base::operator delete (void *rawMemory,<br />

std::size_t size) throw()<br />

{<br />

113

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!