Управление памятью

  • 04 нояб. 2010 г.
  • 2204 Слова
Управление памятью — одна из самых мистических тем в компьютерных технологиях; то, над чем
бьются выдающиеся умы в великих университетах. Я не претендую на глубокое освещение темы в
целом. В оставшейся части книги мы посмотрим, как синтаксис и идиомы С++ помогают построить
основу для подключения тех алгоритмов и структур данных, которые вы захотите реализовать. Темне
менее, даже краткие примеры из этой и следующей главы могут пригодиться на практике, если ваша
задача не отличается особой сложностью.
Глава начинается с самого важного — перегрузки операторов new и delete. Затем мы рассмотрим
несколько упрощенных, но очень полезных приемов управления памятью в С++. В последующих
главах описываются нетривиальные методы, основанные наидеях этой главы.
Перегрузка операторов new и delete
Многие удивляются тому, что операторы new и delete можно перегружать, как и все остальные
операторы. Понять, как это делается, проще всего на примере.
Простой список свободной памяти
Рассмотрим простой пример. Оператор delete включает освобождаемые блоки в список свободной
памяти. Оператор new сначала пытаетсявыделить блок из списка и обращается к глобальному
оператору new лишь в том случае, если список свободной памяти пуст.
class Foo {
private:
struct FreeNode {
FreeNode* next;
};
static FreeNode* fdFreeList;
public:
void* operator new(size_t bytes)
{
if (fgFreeList == NULL)
return ::operator new(bytes);
FreeNode* node = fgFreeList;
FgFreeList =fgFreeList->next;
return node;
}
void operator delete(void* space)
{
((FreeNode*)space->next = fgFreeList;
fgFreeList = (FreeNode*)space;
}
};
Как вы вскоре убедитесь, приведенный фрагмент неполон, однако он демонстрирует общие принципы
перегрузки операторов new и delete для конкретного класса. Оператор new получает один аргумент,
объем выделяемого блока, и возвращает адресвыделенного блока. Аргументом оператора delete
является адрес освобождаемой области. Не пытайтесь объявлять их виртуальными; компилятор лишь
посмеется над вами. При вызове оператора new компилятор точно знает, с каким классом он имеет
дело, поэтому v-таблица ему не нужна. При вызове оператора delete деструктор определяет, какому
классу этот оператор должен принадлежать. Если вы хотитегарантировать, что будет вызываться
оператор delete производного класса, то виртуальным нужно сделать деструктор, а не оператор
delete. Перегрузки будут унаследованы производными класса Foo, поэтому это повлияет и на процесс выделения/освобождения памяти в них. На практике нередко создается абстрактный базовый
класс, который не делает почти ничего (как и в приведенномпримере) и используется только для
создания классов с данной схемой управления памятью.
class Bar : public Baseclass, public Foo { ... };
Здесь Bar наследует все базовые характеристики типа Baseclass, а нестандартное управление
памятью — от Foo.
Ограничения минимального размера
Перед тем как хвататься за компилятор, необходимо привести в порядок показанный фрагмент. Во-первых, предполагается, что экземпляр Foo содержит по крайней мере не меньше байт, чем
Foo::FreeNode*. Для классов вроде нашего, не имеющего переменных и виртуальных функций, этого
гарантировать нельзя. Он будет иметь определенный размер (во многих компиляторах — два байта),
чтобы объекты обладали уникальным адресом, но по количеству байт он может быть меньше указателя
наFreeNode. Мы должны гарантировать, что размер Foo не меньше размера указателя — для этого
нужно включить в него v-таблицу или хотя бы переменные, дополняющие его до размера указателя.
Производные классы с добавленными переменными
Другая проблема заключается в том, что приведенный фрагмент не работает с производными классами,
в которых добавляются новые переменные. Рассмотрим...
tracking img