Многопроходные и однопроходные компиляторы
Как уже было сказано, процесс компиляции программ состоит из нескольких фаз. В реальных компиляторах состав этих фаз может несколько отличася от рассмотренного выше – некоторые из них могут быть разбиты на составляющие, другие, напротив, объединены в одну фазу. Порядок выполнения фаз компиляции также может меняться в разных вариантах компиляторов. В одном случае компилятор просматривает текст исходной программы, сразу выполняет все фазы компиляции и получает результат – объектный код. В другом варианте он выполняет над исходным текстом только некоторые из фаз компиляции и получает не конечный результат, а набор некоторых промежуточных данных. Эти данные затем снова подвергаются обработке, причем этот процесс может повторяться несколько раз.
Проход – это процесс последовательного чтения компилятором данных из внешней памяти, их обработки и помещения результата работы во внешнюю память. Чаще всего один проход включает в себя выполнение одной или нескольких фаз компиляции. Результатом промежуточных проходов является внутреннее представление исходной программы, результатом последнего прохода – результирующая объектная программа. При выполнении каждого прохода компилятору доступна информация, полученная в результате всех предыдущих проходов. Как правило, он стремится
использовать в первую очередь только информацию, полученную на проходе, непосредственно предшествовавшем текущему, но в принципе может обращаться и к данным от более ранних проходов вплоть до исходного текста программы. Информация, получаемая компилятором при выполнении проходов, недоступна пользователю. Она либо хранится в оперативной памяти, которая освобождается компилятором после завершения процесса трансляции, либо оформляется в виде временных файлов на диске, которые также уничтожаются после завершения работы компилятора. Поэтому человек, работающий с компилятором, может даже не знать, сколько проходов выполняет компилятор – он
всегда видит только текст исходной программы и результирующую объектную программу. Но количество выполняемых проходов – это важная техническая характеристика компилятора, и фирмы – разработчики компиляторов обычно указывают ее в описании своего продукта. Понятно, что разработчики стремятся максимально сократить количество проходов, выполняемых компиляторами. При этом увеличивается скорость работы компилятора, сокращается объем необходимой ему памяти. Однопроходный компилятор, получающий на вход исходную программу и сразу же порождающий результирующую объектную программу – это идеальный вариант.
Однако сократить число проходов не всегда удается. Количество необходимых проходов определяется, прежде всего, грамматикой и семантическими правилами исходного языка. Чем сложнее грамматика языка и чем больше вариантов предполагают семантические правила – тем больше проходов будет
выполнять компилятор. Например, именно поэтому обычно компиляторы с языка Pascal работают быстрее, чем компиляторы с языка С – грамматика языка Pascal более проста, а семантические правила более жесткие. Однопроходные компиляторы – редкость, они возможны только для очень простых языков. Реальные компиляторы выполняют, как правило, от двух до пяти проходов. Таким образом, реальные компиляторы являются многопроходными. Наиболее распространены двух- и трехпроходные компиляторы, например: первый проход – лексический анализ, второй – синтаксический разбор и
семантический анализ, третий – генерация и оптимизация кода (варианты исполнения, конечно, зависят от разработчика). Нередко первый проход компилятора (лексический анализ кода) выполняется параллельно с рдактированием кода исходной программы.