Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Комплект Информатика / Курс лекций.doc
Скачиваний:
128
Добавлен:
22.05.2015
Размер:
4.8 Mб
Скачать

2 Статические и динамические структуры

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

В целом, статическими структурами (static structure) проще управлять, чем динамическими (dynamic structure). Если структура статическая, нам необходимо только обеспечить способы доступа к различным элементам данных и, возможно, способы изменения значений элементов, находящихся на определенных местах. Но в случае динамической структуры также необходимо решать проблемы добавления и удаления элементов данных и поиска пространства в памяти для увеличения размера структуры. Излишнее разрастание плохо организованной структуры может привести к тому, что она целиком будет копироваться в другую, большую по размерам область памяти — а для этого требуется много времени.

3 Указатели

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

Мы уже встречались с концепцией указателей в контексте счетчика команд процессора, который содержит адрес очередной инструкции для выполнения. Фактически, другое название счетчика команд — указатель команд (instruction pointer). Адреса, также называемые URL, которые используются для связи гипертекстовых документов, также могут служить примером концепции указателей, но они указывают местоположения в сети Интернет, а не в оперативной памяти компьютера.

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

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

Рисунок 3 – Рассказы, отсортированные по названию и связанные указателями по автору

Рассмотрим, как указатели обрабатываются на машинном языке.

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

Поскольку мы заранее не знаем содержимого записи стека, код операции 2 не подходит для выполнения нашей задачи. Более того, код операции 1 мы также использовать не можем, так как не знаем адреса данных, ведь адрес вершины стека изменяется по мере выполнения программы. Но мы знаем адрес указателя стека. Таким образом, нам известно расположение ячейки, содержащей адрес данных, которые мы хотим загрузить в регистр. Что нам требуется — это еще один код операции для записи в регистр, где операнд содержит адрес указателя на нужные данные.

Для достижения этой цели разработчик машины из приложения В может воспользоваться кодом операции D. Язык можно дополнить так, чтобы инструкция вида DRXY означала запись в регистр R содержимого ячейки памяти, адрес которой находится по адресу XY (рис. 4). Так, если указатель стека находится в ячейке памяти по адресу АА, то при выполнении инструкции D5AA данные из вершины стека будут загружены в регистр 5.

Эта инструкция, однако, не выполняет операцию выталкивания полностью. От указателя стека необходимо отнять единицу, чтобы он теперь указывал на новую вершину стека. Инструкция должна выполняться следующим образом: программа на машинном языке загрузит указатель стека в регистр, отнимет от указателя единицу и запишет результат обратно в память.

Рисунок 4 – Первая попытка расширения машинного языка для использования указателей

Используя в качестве указателя стека один из регистров вместо ячейки памяти, мы можем сократить количество перемещений указателя стека между регистрами и памятью. Однако это означает, что нам необходимо переопределить инструкцию загрузки, чтобы она искала указатель в регистре, а не в оперативной памяти. Таким образом, вместо предыдущего подхода разработчик машины может определить инструкции с кодом операции D в форме DR0S, что означает запись в регистр R содержимого ячейки памяти, на которую указывает регистр S (рис. 5). Тогда, выполнив эту инструкцию и команду вычитания единицы из значения в регистре S, мы выполним полную операцию выталкивания.

Рисунок 5 – Загрузка в регистр значения из ячейки памяти, найденной при помощи указателя, хранящегося в регистре

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

С этими расширениями машинного языка из приложения В будут реализованы три способа адресации. В первом операнд инструкции содержит данные (код операции 2). Во втором случае в операнде инструкции содержится адрес данных (коды операции 1 и 3). В третьем случае в операнде инструкции хранится адрес указателя на данные (коды операции D и Е). Эти способы называются непосредственной адресацией (immediate addressing), прямой адресацией (direct addressing) и косвенной адресацией (indirect addressing) соответственно. Все они часто встречаются в современных машинных языках.

Проблема указателей. Также как использование блок-схем привело к созданию запутанных алгоритмов, а бессистемное употребление оператора goto — к появлению плохо спроектированных программ, необдуманное использование указателей может служить причиной излишней сложности и подверженности структур данных ошибкам. Чтобы внести порядок в этот хаос, многие языки программирования ограничивают гибкость указателей. Например, в языке Java указатели в общей форме запрещены. Вместо этого он разрешает использование ограниченной формы указателей, называемой ссылкой. Одно из различий состоит в том, что ссылку нельзя изменить, применив арифметическую операцию. То есть если Java-программист захочет переместить ссылку Next на следующую запись в непрерывном списке, ему придется использовать оператор типа переназначить Next на следующую запись списка, тогда как С-программист может применить оператор, эквивалентный следующему: присвоить Next значение Next + 1

Обратите внимание, что оператор Java лучше отражает желаемую цель. Более того, для успешного выполнения оператора Java в списке должна существовать еще хотя бы одна запись, а в случае, если Next уже указывает на последнюю запись списка, оператор С приведет к тому, что Next будет содержать адрес каких-то не относящихся к списку данных — эту ошибку часто делают начинающие и даже опытные С - программисты.