В чем состоит компиляция программы

В процессе разработки программного обеспечения используется большое количество инструментов. Для простоты, все используемые инструментальные средства можно разделить на 4 группы.

Необходимые

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

компиляторы и интерпритаторы;

компоновщики или редакторы связей (linkers);

Часто используемые

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

утилиты автоматической сборки проекта;

программы создания инсталляторов;

программы поддержки версий;

программы создания файлов помощи (документации).

Специализированные

Эти инструментальные средства используются в исключительных случаях, решают довольно специфичные задачи:

программы отслеживания зависимостей;

Уроки Java для начинающих #3 | Компиляция программы

программы отслеживания активности системы и изменений, происходящих в системе;

программы-вериферы и контейнеры (создают виртуальную среду для отдельных классов программ, в которой можно исследовать поведение программы).

Интегрированные среды разработки

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

Текстовые редакторы

Текстовые редакторы относятся к необходимым инструментальным средствам. Это так, потому что не имея текстового редактора, мы не сможем редактировать исходный текст на языке программирования. Важно понимать, что в этом контексте под текстовым редактором понимается не что-то вроде Microsoft Word с его многообразием функций для форматирования текста, таблиц и т.п.. Текстовый редактор для программирования должен обеспечивать немного другой функционал:

— возможность сохранения документов в требуемом формате (поддержка нужных расширений и т.п.);

— возможность подсветки синтаксиса (выделение ключевых слов, констант и т.п.);

— возможность копирования, вставки, замены фрагментов текста;

— возможность автодополнения вводимого текста, отображения вариантов для вызываемых функций и т.п.;

— возможности автоформатирования вводимого текста (автоматически создавать отступы в теле функции и т.п.).

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

Компиляция. Как работает компилятор

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

Пожалуй, нет смысла описывать тут процесс открытия и сохранения документов в текстовом редакторе. Сделаем акцент на другой момент при работе с исходными текстами программ. Дело в том, что русские символы могут кодироваться по-разному. Существует несколько однобайтных кодировок (например: cp-1251, cp-866, koi8-r) и несколько двухбайтных (utf-8, utf-16).

Получается, что если сохранить файл в одной кодировке, а открыть, подразумевая, что он в другой — русский текст сложно будет прочитать. Это — основной источник проблем при работе с текстовыми редакторами. Несмотря на повсеместный переход на UTF-8, проблема всё еще актуальна и нужно помнить о том, какая у вас кодовая страница и поддерживает ли её редактор. Таким образом, практически обязательным требованием для текстового редактора является поддержка разных кодировок текста и способность преобразования текста из одной в другую.

Задание на дом: Требуется найти в интернете требуемый редактор и создать что-то типа его презентации (составить краткий доклад о поддерживаемых функциях и привести несколько снимков экрана).

Компиляторы, интерпретаторы, компоновщики

Итак, у нас есть исходный текст, написанный на языке программирования. Он составлен в соответствиями с правилами языка и готов к дальнейшей обработке. Каким образом компьютер может выполнить этот исходный текст? Тут нам на помощь приходит программа, именуемая интерпретатор или компилятор. Обе эти программы выполняют схожие задачи (преобразование исходных текстов в машинный код), но подходят к ним по-разному.

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

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

— синтаксический (грамматический) анализ. Последовательность лексем преобразуется в дерево разбора.

— семантический анализ. Дерево разбора обрабатывается с целью установления его смысла — например, привязка идентификаторов к их декларациям, типам, проверка совместимости, определение типов выражений и т. д. Результат обычно называется «промежуточным представлением».

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

— генерация кода. Из промежуточного представления порождается код на целевом языке.

Таким образом, «пропустив» исходный текст через компилятор мы (при отсутствии ошибок на этапах анализа) получим объектный код, который почти готов к выполнению. Компиляторы одних языков программирования (например, С) генерируют код для конкретного типа процессора (например Intel), в то время как компиляторы других языков программирования (например Java) генерируют код для выполнения в специальной виртуальной машине. Почему компилятор не генерирует сразу готовый код?

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

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

Интерпретатор тоже преобразует текст на языке высокого уровня, но делает это построчно. Как только первая строка преобразована — она тут же запускается на выполнение. У такого подхода есть как достоинства, так и недостатки.

— боìльшая переносимость интерпретируемых программ — программа будет работать на любой платформе, на которой есть соответствующий интерпретатор.

— как правило, более совершенные и наглядные средства диагностики ошибок в исходных кодах.

— упрощение отладки исходных кодов программ.

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

— интерпретируемая программа не может выполняться отдельно без программы-интерпретатора. Сам интерпретатор при этом может быть очень компактным.

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

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

Читайте также:
G96 fanuc токарный пример программы

Чтобы избавиться от части недостатков, для некоторых языков программирования применяют интерпретаторы компилируемого типа. Они построчно выполняют код, который создал компилятор. Если вы помните, выше мы говорили, что некоторые компиляторы дают на выходе код не конкретного процессора, а какой-либо виртуальной машины. Это как раз тот случай.

Компоновщик, известный так же как редактор связей, (linker, link editor) занимается созданием готового исполняемого файла из «кусочков» объектного кода. Выше, в разделе компиляции мы рассматривали ситуацию, когда проще выполнить компиляцию по частям. Если был применен такой подход, то окончательную сборку выполняет компоновщик. Как-правило, компоновщик является частью компилятора.

Для связывания модулей компоновщик использует таблицы имён, созданные компилятором в каждом из объектных модулей. Такие имена могут быть двух типов:

— определённые или экспортируемые имена — функции и переменные, определённые в данном модуле и предоставляемые для использования другим модулям;

— неопределённые или импортируемые имена — функции и переменные, на которые ссылается модуль, но не определяет их внутри себя.

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

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

Источник: poisk-ru.ru

Языки программирования

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

Задачи компилятора

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

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

Синтаксический анализ воссоздает синтаксическую структуру программы.

Проверка правильности включает контроль типов и другие согласованные проверки. Eiffel , например, имеет примерно 90 «правил контроля правильности», таких как:

  • в операторах присваивания и при передаче аргументов тип источника должен соответствовать типу цели;
  • класс B не может назвать класс A своим родителем, если родителем B назван предок A . Это правило защищает от возникновения циклов при наследовании.

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

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

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

  • распределение регистров – оптимизация периода выполнения. Математически 3times b+aимеет то же значение, что и a+3times b, но одна из этих форм может выполняться быстрее другой из-за более эффективного распределения регистров. Оптимизация приводит к тому, что генератор кода выберет быстрейший вариант;
  • удаление участков «мертвого кода». Если оптимизатор обнаружит, что некоторые участки кода программы никогда не будут выполняться во время исполнения, то он может удалить соответствующий сгенерированный код, а еще лучше – вообще не генерировать его с самого начала.

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

Фундаментальные структуры данных

Задачи лексического и синтаксического анализа взаимосвязаны: «парсер» вызывает «лексер» (сокращения для синтаксического и лексического анализатора) для получения очередной лексемы. Главным результатом работы парсера является АСТ (абстрактное синтаксическое дерево), которое задает структуру программы, очищенную от чисто текстуальных свойств, таких как ключевые слова.

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

Для типичной организации современных компиляторов в задачу лексера и парсера входит создание АСТ и таблицы идентификаторов, инициализированной базисной чисто синтаксической информацией. Оставшиеся компоненты компилятора обогащают, часто говорят – декорируют, эти структуры данных более глубокой семантической информацией.

Проходы

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

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

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

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

Компилятор как инструмент верификации

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

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

Читайте также:
Лучшая программа для обрезки видео без потери качества

Загрузка и связывание

Программам в машинном коде необходимы адреса памяти. Присваивание x: = expr будет помещать значение выражения expr по адресу памяти, связанному с x . В условном операторе if c then a … управление будет передаваться в зависимости от вычисленного значения условия c по различным адресам, связанным с соответствующими участками кода. Вызов метода r(…) или x.r(…) приведет к передаче управления по адресу соответствующего кода, а затем вернет управление в точку, следующую за вызовом метода.

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

  • Когда обрабатываются элементы программы, принадлежащие некоторому модулю, например, методы класса, компилятор управляет только относительными адресами в области памяти, отведенной модулю. Например, когда он обрабатывает неквалифицированный вызов r(…) , появляющийся в методе того же класса C , что и r , компилятору известно смещение кода для r внутри области, отведенной для C . Компилятору неизвестны соответствующие абсолютные адреса, которые могут быть установлены только в момент загрузки и могут меняться от одного сеанса выполнения к другому.
  • Для элементов другого модуля адреса будут смещаться относительно начального адреса этого модуля. Если бы вся программа компилировалась полностью, то это соответствовало бы уже рассмотренной нами ситуации, поскольку компилятор мог бы определить раскладки для всех модулей. Но часто желательно допускать раздельную компиляцию, когда модули компилируются независимо друг от друга и только потом объединяются в единую программу.

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

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

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

Время выполнения

Сегодняшние амбициозные языки программирования требуют не только изощренного инструментария в период компиляции (включая изощренный компилятор), но также серьезной поддержки на этапе выполнения. Когда программа выполняется, ей необходимо динамическое распределение памяти (для таких операторов, как конструкторы класса create x ), нужна автоматическая сборка мусора, которая освобождает память от объектов, ставших недоступными программе, требуется обработка исключений и поддержка ввода и вывода. Аппаратура обычно напрямую не поддерживает эти механизмы. Эффективное управление памятью, в частности, основано на сложных алгоритмах и структурах данных.

Это потребности всех программ, а потому было бы неразумно, если бы компилятор генерировал соответствующий код для каждой программы. Вместо этого, когда программе понадобится одно из таких свойств, компилятор включает в генерируемый код вызов соответствующего метода из библиотеки, известной как система времени исполнения, или библиотека времени исполнения, или просто исполняемая среда (the run time). Программный код перед выполнением должен быть скомпонован с библиотекой исполняемой среды.

Другой способ установки роли исполняемой среды состоит в том, чтобы связать ее с понятием виртуальной машины. В то время как типичные машинные команды и видимые свойства «железа» – не считая скорости и размеров – не слишком изменились за последние пятьдесят лет, языкам программирования требуются более продвинутые виртуальные машины.

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

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

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

Отладчики и инструментарий выполнения

После того, как программа скомпилирована и скомпонована, возникает естественное желание запустить ее на выполнение. Окончательная версия программы типично является исполняемым exe-файлом.

Но до завершения разработки необходимо контролировать выполнение, чтобы иметь возможность, например, в случае возникновения ошибки, исследовать контекст выполнения – анализировать содержимое объектов в точке ее проявления. Для этого необходим отладчик (debugger). Роль отладчика не только в том, чтобы отладить программу, разыскивая в ней ошибки (bug). Современные отладчики представляют инструментарий, предназначенный, как теперь модно говорить, для мониторинга за процессом выполнения программы.

Типичный отладчик включает такие средства, как задание точек останова (прерывания) в исходном тексте, запуск, прерывание, продолжение и завершение выполнения. Когда выполнение останавливается, что может быть вызвано одной из трех причин: достижением точки останова, возникновением ошибки или прерыванием, инициированным пользователем, – отладчик позволит исследовать код, приведший к текущему состоянию, проанализировать структуру объектов, видя их содержимое, динамически вычислять выражения, выполнять различные другие проверки программы и ее данных. В некоторых случаях отладчики, такие как отладчик EiffelStudio, позволяют выполнить откат по программе, чтобы повторно в пошаговом режиме проследить и понять причину, приведшую к такому состоянию (появлению ошибки). Следующий рисунок показывает типичное состояние отладчика EiffelStudio.

Наличие хороших отладчиков не дает нам права на «плохое» программирование (не надейтесь, что любая ошибка будет обнаружена в процессе отладки). Контроль выполнения может помочь только в некоторых случаях из несметного числа возможных сеансов выполнения.

Сеанс отладки в EiffelStudio


увеличить изображение
Рис. 3.10. Сеанс отладки в EiffelStudio

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

3.4. Верификация и проверка правильности

Отладчики типично поддерживают проверку программы, выполняемую самими разработчиками. Перед тем, как работа над программой будет считаться законченной и она может быть передана пользователям, программа обычно подвергается систематическому процессу верификации и проверки правильности ( verification and validation – V V будет уделено больше внимания. На данный момент просто стоит понимать, что соответствующие средства относятся к двум разным категориям.

  • Статические анализаторы основываются на программных текстах. Примером является принуждение соблюдений правил согласованности типов и других ограничений правильности, выполняемых компилятором. Средства статического анализа на этом не останавливаются и идут далее в направлении возможного доказательства корректности программы.
  • Динамические методы должны выполнять программу, тестируя ее на соответствие ожидаемым результатам.
Читайте также:
LG программы не работают

Источник: intuit.ru

Компиляция и запуск С-программ

Компиля́ тор — программа или техническое средство, выполняющее компиляцию.

Компиляция — трансляция программы, составленной на исходном языке высокого уровня, в эквивалентную программу на низкоуровневом языке, близком машинному коду (абсолютный код, объектный модуль, иногда на язык ассемблера). Входной информацией для компилятора (исходный код) является описание алгоритма или программа на проблемно-ориентированном языке, а на выходе компилятора — эквивалентное описание алгоритма на машинно-ориентированном языке (объектный код).

Компилировать — проводить трансляцию машинной программы с проблемно-ориентированного языка на машинно-ориентированный язык.

Виды компиляторов

Векторизующий. Транслирует исходный код в машинный код компьютеров, оснащённых векторным процессором.

Гибкий. Сконструирован по модульному принципу, управляется таблицами и запрограммирован на языке высокого уровня или реализован с помощью компилятора компиляторов.

Диалоговый. См.: диалоговый транслятор.

Инкрементальный. Повторно транслирует фрагменты программы и дополнения к ней без перекомпиляции всей программы.

Интерпретирующий (пошаговый). Последовательно выполняет независимую компиляцию каждого отдельного оператора (команды) исходной программы.

Компилятор компиляторов. Транслятор, воспринимающий формальное описание языка программирования и генерирующий компилятор для этого языка.

Отладочный. Устраняет отдельные виды синтаксических ошибок.

Резидентный. Постоянно находится в оперативной памяти и доступен для повторного использования многими задачами.

Самокомпилируемый. Написан на том же языке, с которого осуществляется трансляция.

Универсальный. Основан на формальном описании синтаксиса и семантики входного языка. Составными частями такого компилятора являются: ядро, синтаксический и семантический загрузчики.

Виды компиляции

Пакетная. Компиляция нескольких исходных модулей в одном пункте задания.

Построчная. То же, что и интерпретация.

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

Основы

Большинство компиляторов переводит программу с некоторого высокоуровневого языка программирования в машинный код, который может быть непосредственно выполнен процессором. Как правило, этот код также ориентирован на исполнение в среде конкретной операционной системы, поскольку использует предоставляемые ею возможности (системные вызовы, библиотеки функций). Архитектура (набор программно-аппаратных средств), для которой производится компиляция, называется целевой машиной.

Некоторые компиляторы переводят программу с языка высокого уровня не прямо в машинный код, а на язык ассемблера. Результатом работы других компиляторов является программа на специально созданном низкоуровневом языке, подлежащем интерпретации виртуальной машиной. Такой язык называется псевдокодом или байт-кодом.

Как правило, он не является машинным кодом какого-либо компьютера и программы на нём могут исполняться на различных архитектурах, где имеется соответствующая виртуальная машина, но в некоторых случаях создаются аппаратные платформы, напрямую поддерживающие псевдокод какого-либо языка. Например, псевдокод языка Java называется байт-кодом Java (англ. Java bytecode) и выполняется в Java Virtual Machine, для его прямого исполнения была создана спецификация процессора picoJava. Для платформы.NET Framework псевдокод называется Common Intermediate Language (CIL), а среда исполнения — Common Language Runtime (CLR).

Виртуальная машина может сразу интерпретировать псевдокод, либо транслировать его в машинный код непосредственно перед исполнением. Последнее называется динамической или JIT-компиляцией. CIL-код также компилируется в код целевой машины JIT-компилятором, а библиотеки.NET Framework компилируются заранее.

Для каждой целевой машины (IBM, Apple, Sun и т. д.) и каждой операционной системы или семейства операционных систем, работающих на целевой машине, требуется написание своего компилятора. Существуют также так называемые кросс-компиляторы, позволяющие на одной машине и в среде одной ОС генерировать код, предназначенный для выполнения на другой целевой машине и/или в среде другой ОС. Кроме того, компиляторы могут оптимизировать код под разные модели из одного семейства процессоров (путём поддержки специфичных для этих моделей особенностей или расширений наборов инструкций). Например, код, скомпилированный под процессоры семейства Pentium, может учитывать особенности распараллеливания инструкций и использовать их специфичные расширения — MMX, SSE и т. п.

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

Более эффективно декомпилируются программы в байт-кодах — например, существует довольно надёжный декомпилятор для Flash. Разновидностью декомпилирования является дизассемблирование машинного кода в код на языке ассемблера, который почти всегда выполняется успешно (при этом сложность может представлять самомодифицирующийся код или код, в котором собственно код и данные не разделены). Связано это с тем, что между кодами машинных команд и командами ассемблера имеется практически взаимно-однозначное соответствие.

Структура компилятора

Процесс компиляции состоит из следующих этапов:

Лексический анализ. На этом этапе последовательность символов исходного файла преобразуется в последовательность лексем.

Лексема (информатика) — последовательность допустимых символов языка программирования, имеющая смысл для транслятора.

Синтаксический (грамматический) анализ. Последовательность лексем преобразуется в дерево разбора.

Семантический анализ. Дерево разбора обрабатывается с целью установления его семантики (смысла) — например, привязка идентификаторов к их декларациям, типам, проверка совместимости, определение типов выражений и т. д. Результат обычно называется «промежуточным представлением/кодом», и может быть дополненным деревом разбора, новым деревом, абстрактным набором команд или чем-то ещё, удобным для дальнейшей обработки.

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

Генерация кода. Из промежуточного представления порождается код на целевом языке.

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

Раздельная компиляция

Раздельная компиляция (англ. separate compilation) — трансляция частей программы по отдельности с последующим объединением их компоновщиком в единый загрузочный модуль.

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

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

Интересные факты

На заре развития компьютеров первые компиляторы (трансляторы) называли «программирующими программами» (так как в тот момент программой считался только машинный код, а «программирующая программа» была способна из человеческого текста сделать машинный код, то есть запрограммировать ЭВМ).

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

В простейшем случае откомпилировать программу можно, запуская компилятор

командой gcc имя исходного файла

Если программа была написана без ошибок, то компилятор создаст исполняемый файл с именем a.out. Изменить имя создаваемого исполняемого файла можно, задав его с помощью опции -o:

Источник: lektsia.com

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru