Программирование >>  Синтаксис инициирования исключений 

1 ... 65 66 67 [ 68 ] 69 70 71 ... 82


внутренние из кучи. Как будет показано в последних главах, это играет важную роль в схемах уплотнения и сборки мусора.

В двух следующих главах эта методика будет применяться довольно часто. Перемещаемые объекты отделяются от объектов, остающихся на одном месте. Ведущие указатели, доступные из стека, находятся в одном пространстве памяти, а доступные из других процессов - в другом.

Пространства стека и кучи

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



Уплотнение памяти


Представьте себе обычное управление памятью в С++: вы зажигаете благовония, приносите жертву божествам операционной системы и удаляете объект. Если все идет нормально, объект будет должным образом деинициализирован и уничтожен и никто никогда не попытается им воспользоваться. Ха! Всем известно, что в реальной жизни так не бывает.

Одна из главных достопримечательностей динамических языков - таких как SmallTalk и Lisp - не имеет никакого отношения к самому языку, а лишь к тому, что он удаляет объекты за вас, автоматически и надежно. Выходит потрясающая экономия времени и энергии, не говоря уже о благовониях и жертвах. Можно ли то же самое сделать на С++? Вместо прямолинейного да или нет я отвечу: Все зависит от того, сколько труда вы хотите вложить в решение .

В этой главе начинается настоящее веселье - вы увидите, как перемещать объекты в памяти, чтобы свести к минимуму фрагментацию свободной памяти. Надеюсь, при виде моих алгоритмов никто не будет кататься по полу от смеха. Заодно мы подготовим сцену для полноценных алгоритмов сборки мусора, о которых речь пойдет в следующей главе.

Поиск указателей

Помимо подсчета ссылок и нестандартных операторов new и delete в большинстве стратегий управления памятью сочетаются две методики: определение момента, когда доступ к объекту становится невозможным, для его автоматического уничтожения (сборка мусора) и перемещение объектов в памяти (уплотнение). В свою очередь, эти стратегии зависят от того, что в других языках делается легко и просто, но оказывается дьявольски сложным в С++ - от задачи поиска всех указателей на объекты.

Поиск указателей в программе на С++ чрезвычайно сложен, поскольку компилятор не оставляет никаких инструкций на этот счет. Более того, в С++ программа может получить адрес части объекта, поэтому некоторые указатели могут ссылаться на середину большого объекта.

Мама, откуда берутся указатели?

В С++ существуют невероятно разнообразные способы получения указателей. Одни связаны с конкретным представлением объектов в памяти, другие - с наследованием, третьи - с переменными классов. Конечно, самый очевидный способ - это нахождение адреса. А теперь давайте рассмотрим другие, не столь тривиальные способы.

Адреса переменных класса

Имея объект, вы можете получить адрес переменной класса, поспользоваться им или передать другому объекту.

class Foo { private:

int x;

String y;



public:

int& X() { return x; } Ссылка на x String* Name() { return &y; } Адрес y

Каждый экземпляр Foo выглядит примерно так, как показано на представленной ниже диаграмме (вообще говоря, все зависит от компилятора, но в большинстве компиляторов дело обстоит именно так):

Foo* -

vtable

x& -

y* -

Как правило, несколько первых байт занимает указатель на v-таблицу для класса данного объекта. За ним следуют переменные класса в порядке их объявления. Если вы получаете адрес переменной класса в виде ссылки или указателя, возникает указатель на середину объекта.

Адреса базовых классов

Наследование также может вызвать массу положительных эмоций. class A {...}; Один базовый класс class B {...}; Другой базовый класс

class C : public A, public B {...}; Множественное наследование

При одиночном наследовании преобразование от derived* к base* (где base - базовый, а derived - производный класс) адрес остается прежним, даже если компилятор полагает, что тип изменился. При множественном наследовании дело обстоит несколько сложнее.

C* c = new C;

A* a = c; Преобразование от производного к первому базовому классу B* b = c; Преобразование от производного ко второму базовому классу

cout << c << endl; cout << a << endl; cout << b << endl;

Вроде бы все просто, но в действительности компилятор проделывает довольно-таки хитрый фокус. При преобразовании C* к A* указатель остается прежним. Однако при преобразовании C* к B* компилятор действительно изменяет адрес. Это связано с тем, как объект хранится в памяти (структура объектов зависит от компилятора, но сказанное относится ко всем компиляторам, с которыми я работал).

C*, A* ->


Компилятор строит объект в порядке появления базовых классов, за которыми следует производный класс. Когда компилятор преобразует C* к A*, он словно набрасывает черное покрывало на составляющие B и C и убеждает клиентский код, что тот имеет дело с самым настоящим A.

A*(c) ->




1 ... 65 66 67 [ 68 ] 69 70 71 ... 82

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика