Организация и функционирование компьютеров

         

Динамические структуры данных


До сих пор мы встречались с переменными, которые описывались в разделе объявления переменных программы или процедур и функций и которым в программе приписано определенное имя. Таким переменным  приписан простой или составной тип, который однозначно определяет объем памяти, необходимой для хранения переменной. Память для поименованных переменных выделяется при компиляции программы. Такая память называется статической. В некоторых ситуациях статическая память имеет серьезные недостатки. Рассмотрим подробнее некоторые аспекты компиляции программы и структуру программного кода.

Код программы состоит из трех автономных частей. Первая часть представляет собой исполняемую часть программы и называется программной секцией. Она включает реализацию операторов программы и всех процедур и функций программы в виде машинных команд. Каждая процедура или функция задается смещением относительно начала программной секции.

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

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

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

Выходом из положения служит использование динамических переменных. Динамической переменной называется переменная, память для которой выделяется в процессе работы программы по специальному запросу. Занимается распределением динамической памяти операционная система. При запросе на размещение динамической переменной операционная система выделяет ей память из числа свободных ячеек и помечает их как занятые. После того, как данная переменная больше не нужна программе, ее можно освободить и снова сделать выделенные ей блоки свободными. Та же память может быть выделена другой переменной той же или даже другой программы (описанная функция операционной системы называется управлением памятью и является одной из важнейших).

В Паскале выделение динамической памяти осуществляется функцией new, а освобождение - функцией dispose. Однако сдесь возникают некоторые концептуальные трудности. Уже говорилось, что транслятор заменяет каждое имя объекта адресом данного объекта (на самом деле смещением от начала соответствующей секции). Но ведь адрес динамической переменной появляется только в момент ее размещения и при компиляции неизвестен. Следовательно, программный код должен быть устроен таким образом, чтобы можно было совершать операции над непоименованными переменными, адрес которых в момент компиляции программы не известен.

Указанное противоречие разрешается использованием в языках программирования специальных переменных - указателей или ссылок. Значением указателя является адрес того участка памяти, где в настоящий момент храниться значение динамической переменной. Следовательно, для указателя существенны две области памяти: одна, где указатель хранится, и другая, на которую указатель указывает. Отметим, что сам указатель является статической переменной и память для него выделяется заранее (это два или четыре байта). Очевидно, что использовать динамическую память имеет смысл тогда, когда сама динамическая переменная требует гораздо большего объема памяти (обычно это массив или запись).


Содержание раздела