А.В. Ахо, М.С. Лам, Р. Сети, Дж. Д. Ульман - Компиляторы - принципы, технологии и инструментарий (1114947), страница 8
Текст из файла (страница 8)
Язьти четвертого поколения — это языки программирования, разработанные для конкретных применений, например ХОМА!Э вЂ” для генерации отчетов, Щ. — для запросов к базам данных и Розгзсйрг — для форматирования текстов. Термин языки пятого поколения применяется к языкам программирования, основанным на логике или ограничениях, таким как Рго!оя и ОРК5. Еще в одной классификации языков программирования используются термин императивный (1шрегапче) по отношению к языкам программирования, в которых программа указывает, как должны выполняться вычисления, и термин декларативный (бес!агайче) по отношению к языкам, которые указывают, какие вычисления должны быть выполнены.
Языки программирования наподобие С, С++, С№ и 3ача являются императивными. В императивных языках имеется понятие состояния программы и инструкции, которые изменяют это состояние. Функциональные языки, такие как МЬ и Наз!ге!1, а также логические языки типа Рго!оя, часто рассматриваются как декларативные. Термин язык фон Неймана (чоп Ыешпапп 1апяцаяе) применим к языкам программирования, вычислительная модель которых основана на вычислительной архитектуре фон Неймана. Многие современные языки программирования, такие как Роптал и С, являются языками фон Неймана.
Обьектно-ориентированные языки — это языки программирования, поддерживающие объектно-ориентированное программирование, стиль программирования, в котором программа состоит из набора объектов, взаимодействующих друг с другом. Главными наиболее ранними объектно-ориентированными языками программирования стали Бипц!а 67 и Вша!!га1!г; примерами более поздних объектноориентированных языков программирования являются С++, С№, 5ача и йлЬу. Языки сценариев ("скриптов") — это интерпретируемые языки с высокоуровневыми операторами, разработанными для "склеивания" вычислений. Такие вычисления изначально назывались сценариями (зсйрг).
Популярными примерами Глава 1. Введение в компиляцию языков сценариев являются Ав1с, .1ачаБсир1, Рег1, РНР, РутЬоп, КцЬу, КЕХХ и Тс!. Программы, написанные на языках сценариев, чаше всего существенно короче эквивалентных программ, написанных на языках программирования наподобие С. 1.3.2 Влияние на компиляторы Поскольку разработка языков программирования и разработка компиляторов тесно связаны между собой, новые достижения в области языков программирования приводят к новым требованиям, возникающим перед разработчиками компиляторов, которые должны придумывать алгоритмы и представления для трансляции и поддержки новых возможностей языка. Кроме того, с ! 940-х годов произошли существенные изменения и в архитектуре вычислительных систем, так что разработчики компиляторов должны не только учитывать новые свойства языков программирования, но и разрабатывать такие алгоритмы трансляции, которые смогут максимально использовать преимущества новых аппаратных возможностей.
Компиляторы могут способствовать использованию высокоуровневых языков программирования, минимизируя накладные расходы времени выполнения программ, написанных на этих языках. Они играют важную роль в эффективном использовании высокопроизводительной архитектуры компьютера пользовательскими приложениями. В действительности производительность вычислительной системы настолько зависит от технологии компиляции, что компиляторы используются в качестве инструмента для оценки архитектурных концепций перед созданием компьютера.
Написание компилятора представляет настоящий вызов для программиста. Компилятор сам по себе — большая и сложная программа. Кроме того, многие современные системы обработки языков работают с разными языками и целевыми машинами в пределах одного пакета, т.е. представляют собой набор компиляторов, состоящих, возможно, из миллионов строк кода. Соответственно, при разработке и создании современных языковых процессоров определяющую роль играют методы программотехники. Компилятор обязан корректно транслировать потенциально бесконечное множество программ, которые могут быть написаны на соответствующем языке программирования.
Задача генерации оптимального целевого кода из исходной программы в общем случае неразрешима; таким образом, разработчики компиляторов должны искать компромиссные решения о том, какие эвристики следует использовать для генерации эффективного кода. Изучение компиляторов — это также изучение вопроса о том, насколько теория соответствует практике, как вы увидите в разделе 1.4. Цель данной книги — изучение методологии и фундаментальных идей, использующихся при разработке компиляторов. Мы не ставили задачу обучить читателя 45 1.4. "Компнлятороведение" всем имеющимся алгоритмам и методам, которые могут быть использованы для создания современной системы обработки языка. Однако прочитавшие эту кни- гу получат базовые знания в количестве, достаточном для понимания того, как относительно просто создать собственный компилятор.
1.3.3 Упражнения к разделу 1.3 Упражнение 1.3.1. Укажите, какие термины из списка б) декларативный в) фон Неймана д) функциональный е) третьего поколения з) сценариев а) императивный г) объектно-ориентированный ж) четвертого поколения применимы к языкам программирования из списка 1) С 2) Сз+ 3) СоЬо! 4) Рог!гав 5) )ака 6) Ывр 7) М1.
8) Рег! 9) Ру1Ьоп 1О) ЧВ 1.4 "Ком пилятороведение" 1.4.1 Моделирование при проектировании и реализации компилятора Изучение компиляторов, в основном, заключается в изучении способов разработки математических моделей и выбора правильных алгоритмов, балансируя Разработка компиляторов полна красивых примеров решения сложных задач, возникающих при реальной работе над компиляторами, путем математического абстрагирования.
Они служат прекрасной иллюстрацией того, как для решения задач могут быть применены абстракции: для этого надо для конкретной задачи сформулировать математическую абстракцию, которая содержит ключевые характеристики задачи, и решить ее с применением математических методов. Формулировка задачи должна основываться на четком понимании характеристик компьютерных программ, а решение должно быть проверено и уточнено эмпирически. Компилятор должен принимать все исходные программы, которые соответствуют спецификации языка; множество исходных программ бесконечно, а сама программа может быть очень большой, состоящей, возможно, из миллионов строк. Любые преобразования, выполняемые компилятором при трансляции исходной программы, должны сохранить неизменным смысл компилируемой программы.
Таким образом, разработчики компиляторов оказывают влияние не только на создаваемые ими компиляторы, но и на все программы, которые будут скомпилированы их компилятором. Это делает написание компиляторов особенно достойным занятием, но и налагает на программистов особую ответственность.
46 Глава 1. Введение в компиляцию между обобщенностью и мощью, с одной стороны, и простотой и эффективностью — с другой. Некоторые из наиболее фундаментальных моделей — юнечные автоматы и регулярные выражения, о юторых пойдет речь в главе 3. Эти модели полезны для описания лексических единиц программы (ключевых слов, идентификаторов и т.п.) и алгоритмов, используемых компилятором для распознавания этих единиц. К фундаментальным моделям относятся и контекстно-свободные грамматики, используемые для описания синтаксических структур языков программирования, таких как вложенные скобки и управляющие конструкции. Грамматики будут изучаться в главе 4. Аналогично деревья являются важной моделью для представления структуры программы и ее трансляции в объектный код, как вы узнаете из главы 5.
1.4.2 Изучение оптимизации кода Термин "оптимизация'* в проектировании юмпиляторов означает попытки компилятора получить код, более эффективный, чем обычно. "Оптимизация", таким образом, является некорректным названием, поскольку нет способа гарантированно получить код столь же быстрый (или более быстрый), как любой другой код, выполняющий те же задачи. В настоящее время оптимизация кода, выполняемая компилятором, становится все более важной и более сложной.
Она становится более сложной, поскольку архитектуры процессоров также становятся все более сложными, предоставляя все больше возможностей для улучшения способа выполнения кода. Оптимизация становится более важной, поскольку массовое наступление компьютеров с возможностью параллельных вычислений требует существенной оптимизации, иначе их производительность падает на порядки. Вероятное преобладание много- ядерных машин (компьютеров с большим количеством процессоров) требует от компиляторов использования преимуществ многопроцессорности. Очень трудно, если вообще возможно, построить мощный компилятор без "хакерства". Соответственно, вокруг задачи оптимизации кода построена богатая н полезная теория.
Используя точные математические основы, можно убедиться в корректности оптимизации и получить желаемый эффект для всех возможных входных данных. Начиная с главы 9, вы увидите, насколько необходимы такие математические модели, как графы, матрицы и линейное программирование, для построения компилятором хорошо оптимизированного кода. С другой стороны, одной голой теории недостаточно. Как и у многих других задач реального мира, здесь нет окончательного идеального ответа.
В действительности большинство вопросов, задаваемых при оптимизации кода компилятором, оказываются неразрешимыми. Одно из важных проявлений профессионализма в проектировании компиляторов состоит в умении корректно сформулировать ре- 47 Е4. "Компилятороведение" язаемую задачу. Для начала требуются хорошее понимание поведения программ и всестороннее экспериментирование и вычисления для подтверждения интуитивных предположений.
Оптимизации, выполняемые компилятором, должны отвечать трем требованиям проектирования: ° оптимизация должна быть корректной, т.е. сохранять смысл компилируемой программы; ° оптимизация должна повышать производительность многих программ; ° время компиляции должно оставаться в разумных пределах; ° требуемая для реализации оптимизации инженерная работа должна быть осуществима. Невозможно переоценить важность корректности. Написать компилятор, генерирующий быстрый код, — тривиальная задача, если генерируемый код может быть неверным! Оптимизирующие компиляторы настолько сложны, что мы готовы даже заявить, что нет ни одного полностью безошибочного оптимизирующего компилятора! Итак, наиболее важная цель при написании компилятора — это его корректность. Вторая цель состоит в том, чтобы компилятор мог повышать производительность многих программ.
Обычно производительность означает скорость выполнения программы. Если говорить о встраиваемых приложениях, то тут может оказаться желательным также малый размер сгенерированного кода. В случае мобильных устройств желательно, чтобы код минимизировал потребление энергии. Обычно та же оптимизация, которая повышает скорость выполнения, приводит и к экономии энергии. Помимо производительности, важны и такие потребительские аспекты, как сообшения об ошибках и отладка.