Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТЯП, ТВП / ТЯПМТ / Пособие.doc
Скачиваний:
163
Добавлен:
11.05.2015
Размер:
2.37 Mб
Скачать

7. Проектирование компиляторов

7.1. Число проходов

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

Определение.

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

Проходы бывают прямыми или обратными, т.е. за один проход исходный текст можно считать слева направо или справа налево.

Большинство языков, использующих идею описания переменных до их первого использования (Pascal, C++и др.), либо использующих принцип умолчания, в принципе могут быть однопроходными. Однако есть ряд особенностей, которые не позволяют обеспечить компиляцию за один проход. Особенно ясно это можно продемонстрировать на проблеме компиляции взаимно рекурсивных процедур. Допустим, что тело процедурыАсодержит вызов процедурыВ, а процедураВсодержит вызов процедурыА. Если процедураА объявляется первой, то компилятор не будет генерировать код для вызоваВвнутри А, не зная типов параметровВ, и в случае процедуры, возвращающей результат, тип этого результата может потребоваться для идентификации обозначения операции. Единственное разумное решение данной проблемы – позволить компилятору сделать дополнительный проход перед генерацией кода.

Часто увеличение числа проходов компилятора используется искусственно - для уменьшения памяти, занимаемой компилятором в оперативном запоминающем устройстве (ОЗУ). Кроме того, количество проходов зависит не только от особенностей транслируемого языка, но и от используемой ЭВМ и операционного окружения.

Обычные традиционные трансляторы используют от четырехдовосьмипроходов, часть из которых тратится на оптимизацию загрузочного кода. Однако для рассмотрения принципов компиляции достаточно остановиться на одно – (максимум) двухпроходных компиляторах.

7.2. Таблицы символов

Информацию о типе (виде) идентификаторов синтаксический анализатор хранит с помощью таблицы символов. Этими таблицами также пользуются генератор кода для хранения адресов значений во время прогона. В языках, имеющих конечное число типов (видов), информацией о типе может быть простое целое число, представляющее этот тип, а в языках имеющих потенциально бесконечное число типов (С++, Pascalи др.), - указатель на таблицу видов, элементами которого являются структуры, представляющие вид.

Как уже отмечалось, для простых языков (Fortran, Basic и др.), имеющих конечное число типов, каждый из них может быть представлен целым числом. Например, типinteger- посредством 1, а типreal – посредством 2. В этом случае таблица символов имеет вид массива с элементами

Identifier, type.

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

С таблицей символов ассоциируются следующие действия.

  1. Идентификатор, встречающийся впервые, помещается в таблицу символов, его тип определяется по соответствующему описанию.

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

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

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

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

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

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

CAR DOG CAB ASS EGG

таблица примет вид табл.7.1; позиции идентификаторов зависят от порядка их внесения в таблицу.

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

Таблица 7.1.

1

2

3

4

5

6

7

.

ASS

CAR

DOG

CAB

EGG

Обычно выход из конфликтов осуществляется циклической проверкой один за другим элементов таблицы до тех пор, пока не находится идентификатор или пустой элемент. Однако на практике используются и другие методы. Обычно вырабатывается некоторое правило, последовательное применение которого позволило бы (при необходимости) просмотреть все записи таблицы, прежде чем какая-либо из них встретится вторично. Действие, выполняемое функцией хеширования, называют первичным хешированием, а вычисление последующих адресов в таблице –вторичным хешированиемилиперехешированием. Функция перехеширования, рассматриваемая в примере, предполагает просто добавление единицы (циклично) к адресу таблицы. Эта функция, как и все функции хеширования, обладает следующим свойством: еслиn– некий адрес в таблице, аpчисло элементов в таблице, то

все являются адресами, и

.

Описанная выше функция перехеширования определяется процедурой