Рассмотрим эффект, создаваемый инструкциями управления памятью.
Три режима управления объектами
Во-первых, будет полезным расширить рамки дискуссии. Форма управления объектами, используемая для ОО-вычислений, может поддерживаться одним из трех обычно встречаемых режимов: статическим, стековым и динамически распределяемым. Выбор режима определяет, как сущности присоединяются к объектам.
| Напомним, что сущность - это имя в тексте программы, представляющее некоторое значение или совокупность значений в период выполнения. Такие значения являются либо объектами, либо (возможно неопределенными) ссылками на объект. Сущностями являются атрибуты, формальные аргументы подпрограмм, локальные переменные подпрограмм и Result. Термин присоединение описывает связь между сущностью и объектом: на определенном этапе выполнения программы сущность x присоединяется к объекту О, если значение x есть либо О (для x развернутого типа), либо ссылка на О (для x ссылочного типа). Если x присоединен к О, часто говорят также, что О присоединен к x. Ссылка может быть присоединена не более чем к одному объекту, объект может быть присоединен к двум и более ссылкам. Проблема динамических псевдонимов обсуждалась в предыдущей лекции. |
В статическом режиме сущность может быть присоединена максимум к одному объекту в процессе выполнения программы. Эта схема, поддерживаемая в таких языках как Fortran, резервирует место для всех объектов и присоединяет объект к имени раз и навсегда при загрузке программы или в начале ее выполнения.
Рис. 9.1. Статический режим
Статический режим прост и эффективно реализуем архитектурой обычного компьютера. Но он имеет серьезные ограничения:
[x]. Препятствует рекурсии. Рекурсивной программе необходимо иметь несколько одновременно активных копий, каждой со своими экземплярами сущностей.
[x]. Препятствует созданию динамических структур данных. Компилятор должен уметь определять точный размер каждой структуры данных из текста программы. Каждый массив, например, должен в этом случае объявляться статично со своим строгим размером. Это серьезно ограничивает мощность языка: становится невозможным оперировать структурами, растущими в ответ на события выполнения. Приходится резервировать максимально возможную память для каждой из структур - это не только неэффективно, но и довольно опасно. Если размер одной из структур данных недооценен, это, скорее всего, вызовет ошибку выполнения системы.
Второй режим размещения объектов - режим стека. Здесь сущность может быть в реальном времени последовательно присоединяться к нескольким объектам. Механизм выполнения размещает и удаляет эти объекты в порядке "последним пришел, первым ушел". Когда объект удаляется, относящаяся к нему сущность присоединяется вновь к объекту, с которым она была связана до появления нового элемента, если, конечно, такой объект существует.
Рис. 9.2. Режим, основанный на стеке
Основанное на стеке управление объектами сделало популярным Algol 60 и с тех пор поддерживается (часто вместе с другими двумя режимами) в большинстве языков. Такой способ поддерживает рекурсию и динамические массивы, границы которых выясняются в процессе выполнения. В Pascal и C этот механизм не применяется к массивам, как это делается в Algol. Однако разработчикам хотелось бы чаще всего именно массивы распределять таким способом. Тем не менее, даже если этот механизм и может быть применен к массивам, размещение в стеке большинства сложных структур данных невозможно.9.1)
Для сложных структур данных нам нужен третий и последний режим: динамическая память, называемая также "кучей", из-за способа ее использования. Это память, в которой объекты создаются динамически по запросу. Сущности могут динамически присоединяться к разным объектам. Во время компиляции обычно нельзя предсказать, какие объекты будут созданы и присоединены к сущности. Кроме того, объекты могут содержать ссылки на другие объекты.
Рис. 9.3. Динамический режим
Динамическая память позволяет создавать сложные динамические структуры данных, необходимые когда, как обсуждалось в предыдущей лекции, ПО требуется вся мощь методов моделирования.
Использование динамического режима
Динамический режим, очевидно, наиболее общий, и он необходим для ОО-программирования. Его используют многие не ОО-языки. В частности:
[x]. Pascal использует статический режим для массивов, режим, основанный на стеке, для переменных, не являющихся массивами и указателями, динамический режим для указателей. В последнем случае создание объекта выполняется с помощью вызова специальной процедуры создания new.
[x]. Язык C похож на Pascal, но дополнительно вводит динамические массивы и статические переменные, не являющиеся массивами, Язык С динамически размещает переменные типа указатель и массивы, используя библиотечную функцию malloc.
[x]. PL/I поддерживает все модели.
[x]. Lisp системы традиционно были высоко динамичны и полагались большей частью на динамический режим распределения памяти. Одна из наиболее важных операций Lisp, используемая многократно для представления списков, - CONS, создает структуру из двух полей. В первом поле хранится значение элемента, а во втором - указатель на следующий элемент. Здесь CONS, скорее источник новых объектов, чем инструкция их создания.
Повторное использование памяти в трех режимах
Для объектов, созданных как в основанном на стеке режиме, так и в динамическом режиме, возникает вопрос, что делать с неиспользуемыми объектами? Возможно ли память, занятую таким объектом, повторно использовать в более поздних инструкциях создания новых объектов?
В статической модели проблемы не существует: для каждого объекта есть одна навсегда присоединенная сущность. Выполнение требует поддерживать связь с объектом все время, пока сущность активна. Поэтому повторное использование памяти невозможно в настоящей трактовке этого понятия. Однако при острой нехватке памяти похожая технология иногда используется. Если вы уверены, что объекты, присоединенные к двум сущностям, никогда не нужны одновременно, и эти сущности не должны сохранять свои значения между последовательными использованиями, то можно на одной и той же памяти размещать две или более сущности, будучи совершенно увереными в безопасности того, что вы делаете. Эта техника, известная как перекрытие (overlay), достаточно ужасная, все еще практикуется при работе вручную.
| Если все-таки использовать перекрытие, то, конечно, его следует выполнять автоматически, используя специальные инструменты, - слишком велика вероятность ошибки. Главной проблемой остается возможность изменений: решение о перекрытии двух переменных может быть корректным на определенном этапе жизни программы. Неожиданное изменение может сделать его неправильным. Мы столкнемся с похожей проблемой ниже, в технологии сборки мусора. |
В режиме, основанном на стеке, объекты, присоединенные к сущностям, могут быть размещены в стеке. В языках с блочной структурой ситуация упрощается: размещение объектов происходит одновременно для всех сущностей данного блока, допуская использование одного стека для всей программы. Схема действительно элегантна, потому что использует два множества сопутствующих событий:
| Динамическое свойство (событие времени выполнения) | Статическое свойство (положение в тексте программы) | Техника реализации |
|---|---|---|
| Размещение объекта | Начало блока | Вталкивание объектов (один для каждой локальной сущности блока) в стек |
| Удаление объекта | Конец блока | Выталкивание объектов из стека |
Основанное на стеке управление объектами сделало популярным Algol 60 и с тех пор поддерживается (часто вместе с другими двумя режимами) в большинстве языков. Такой способ поддерживает рекурсию и динамические массивы, границы которых выясняются в процессе выполнения. В Pascal и C этот механизм не применяется к массивам, как это делается в Algol. Однако разработчикам хотелось бы чаще всего именно массивы распределять таким способом. Тем не менее, даже если этот механизм и может быть применен к массивам, размещение в стеке большинства сложных структур данных невозможно.Основанное на стеке управление объектами сделало популярным Algol 60 и с тех пор поддерживается (часто вместе с другими двумя режимами) в большинстве языков. Такой способ поддерживает рекурсию и динамические массивы, границы которых выясняются в процессе выполнения. В Pascal и C этот механизм не применяется к массивам, как это делается в Algol. Однако разработчикам хотелось бы чаще всего именно массивы распределять таким способом. Тем не менее, даже если этот механизм и может быть применен к массивам, размещение в стеке большинства сложных структур данных невозможно. 9.1