Основы компиляторов. Компиляция - это процесс, облегчающий общение программиста и вычислительной машины

Компьютеры сами по себе способны выполнять только очень ограниченный набор операций, называемых машинными кодами. В старые времена, когда появились первые компьютеры, программы писались в машинных кодах, представляющих собой последовательности двоичных чисел, однозначно воспринимаемых компьютером. В конце 50-х кодов прошлого века появились первые языки программирования, такие как язык ассемблера и Фортран. Для того, чтобы компьютер мог понять программу, написанную на каком-то языке программирования, необходим переводчик ( транслятор ) такой программы в машинные коды. Отметим, что, если оператор языка ассемблера отображается при трансляции чаще всего 1Некоторые операторы языка ассемблера, например, такие, как операторы ввода/вывода, отображаются в несколько машинных команд. в одну машинную инструкцию, предложения языков более высокого уровня отображаются, вообще говоря, в несколько машинных инструкций.

Трансляторы бывают двух типов: компиляторы ( compiler ) и интерпретаторы ( interpreter ). Процесс компиляции состоит из двух частей: анализа ( analysis ) и синтеза ( synthesis ). Анализирующая часть компилятора разбивает исходную программу на составляющие ее элементы (конструкции языка) и создает промежуточное представление исходной программы. Синтезирующая часть из промежуточного представления создает новую программу, которую компьютер в состоянии понять. Такая программа называется объектной программой. Объектная программа может в дальнейшем выполняться без перетрансляции. В качестве промежуточного представления обычно используются деревья, в частности, так называемые деревья разбора. Под деревом разбора понимается дерево , каждый узел которого соответствует некоторой операции , а сыновья этого узла - операндам.

Интерпретатор

В отличие от компилятора, интерпретатор не создает никакой новой программы, а просто выполняет каждое предложение языка программирования. Можно сказать, что результатом работы интерпретатора является "число".

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

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


Компилятор


Компилятор переводит программы с одного языка на другой. Входом компилятора служит цепочка символов , составляющая исходную программу на языке программирования . Выход компилятора (объектная программа ) также представляет собой цепочку символов, но принадлежащую другому языку , например, языку некоторого компьютера. При этом сам компилятор написан на языке , возможно, отличающемся от первых двух. Будем называть язык исходным языком, язык - целевым языком, а язык - языком реализации. Таким образом, можно говорить о компиляторе как об Pascal и кончая современными объектно-ориентированными языками такими, как C# и Java . Практически каждый язык программирования имеет какие-то особенности с точки зрения создателя транслятора. Однако мы начнем с рассмотрения разнообразных целевых языков компиляторов.

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

Конечно, во многих ВУЗах есть специальные курсы, посвящённые устройству различных компонентов современных компиляторов и интерпретаторов. И современные компиляторы слишком сложны для того, чтобы рассказать о них в одной статье достаточно подробно. Тем не менее, базовые механизмы работы компиляторов остались неизменными со времён выпуска первого компилятора языка Фортран.


С высоты птичьего полёта...

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

Итак, что же это за части? Это лексический анализатор, или сканер; синтаксический анализатор, или парсер; семантический анализатор; а также один или несколько генераторов кода и один или несколько оптимизаторов. Также к компилятору часто относят дополнительные инструменты, нужные для создания исполняемого файла - сборщик и компоновщик.

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


От "А" до "Я"

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

Следом за лексическим анализом может быть препроцессор. Тем, кто знаком с языками программирования C или С++, нет нужды объяснять, в чём состоит его функция. Для остальных же скажу, что основная задача препроцессора - это замена одних лексем другими, которые были заранее определены в тексте программы. Используется препроцессор также для условной компиляции (т.е. когда кусок кода должен быть откомпилирован только при выполнении определённых условий - для определённой платформы, только при отладочном билде и т.д. и т.п.), для выполнения определённых макросов (как в том же C/C++) и некоторых других подобных вещей. Препроцессор не является обязательной частью компилятора, поскольку многие языки программирования не нуждаются в нём.

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

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

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

Современные компиляторы, даже самые слабенькие и плохонькие, поддерживают хотя бы базовую оптимизацию кода. Более мощные коммерческие компиляторы содержат в себе очень мощные алгоритмы оптимизации кода, которые позволяют при некоторых условиях сделать её в разы быстрее. Особенно мощными в плане оптимизации давным-давно тому назад считались компиляторы производства Watcom, сейчас, вроде бы, постепенно восстанавливающие свою былую славу в виде open-source продукта. Потом пальма первенства перешла к компиляторам Intel, и сейчас именно они считаются самыми лучшими компиляторами в плане оптимизации. Что ж, это довольно логично - кому, как ни создателям процессоров, знать, как лучше всего оптимизировать программы для работы на них. Впрочем, не важно, плоха оптимизация в компиляторе или нет - главное, что в любом оптимизирующем компиляторе есть модуль, называемый оптимизатором, который начинает свою работу после генератора промежуточного кода. Справедливости ради стоит сказать, что оптимизатор может работать и после генерации уже исполняемого кода, но в наши дни такая схема встречается уже редко, поскольку производители компиляторов, как правило, выпускают целую линейку подобных продуктов для разных языков программирования и стараются делать оптимизаторы, которые можно встроить в любой из этих компиляторов. Какими именно методами оптимизатор может повышать скорость работы программы - это тема отдельной статьи, которую, возможно, вы когда-нибудь и сможете увидеть на страницах "Компьютерных вестей".

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

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


Дополнительные компоненты

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

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

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

Компоновщик создаёт из того кода, который сгенерировал ассемблер, исполняемые файлы. Даже для одной и той же процессорной архитектуры исполняемые файлы будут отличаться в зависимости от операционной системы. Например, для Windows формат исполняемых файлов - это Portable Executable (PE), а для Linux - Executable Linked File (EXE).


Резюме

Как видите, если смотреть на компиляторы со стороны, то всё в них просто и не вызывает никаких особенно заковыристых вопросов. На практике всё, мягко говоря, немного сложнее. И если вы вдруг решите написать собственный компилятор, то не стоит заранее пугаться, хотя к определённым сложностям нужно, как и в любой новой работе, быть готовым. Я бы лично рекомендовал начинать знакомство с предметом с написания какого-нибудь простого эзотерического языка вроде Brainfuck или Whitespace. Поскольку сам я в своё время интересовался благодаря своему знакомому Марату Духану первым из них, то и вам рекомендую его.

Вообще, если же вы вдруг решили проникнуть глубже в тайны создания компиляторов, то в Интернете для вас найдётся масса литературы - и простой, и академически точной и подробной. Начать можно, например, отсюда: kit.kulichki.net . Хотя сайт уже не обновлялся целую вечность, информация, размещённая на нём, подойдёт для новичка и не устареет ещё не один десяток лет. Вообще, если погуглить, информации найдёте очень много, даже придётся её фильтровать. Так что успехов вам с компиляторами!

Вадим СТАНКЕВИЧ

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

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

Если еще упростить определение, то компилировать - это транслировать машинную программу с проблемно-ориентированного в машинно-ориентированный язык.

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

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

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

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

Гибкий компилятор был разработан на основе модульного принципа. Его управление осуществляется таблицами. Запрограммирован он на высокоуровневом языке. Также возможна его реализация при помощи компилятора компиляторов.

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

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

Который воспринимает формальное описание для языка программирования. Он способен самостоятельно генерировать компилятор для конкретного языка.

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

Резидентному компилятору отведено постоянное место в оперативной памяти, и он доступен при повторном использовании широким спектром задач.

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

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

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

Компиляция Java реализовывается при использовании компиляторов, работающих на самых различных платформах. Это позволяет исходные коды перекомпилировать под потребности операционных систем от разных производителей.

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

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

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

Мы будем компилировать программы, написанные на Си или С++, так как это наиболее используемый язык для программ, которые требуют компиляции. Мы уже немного рассматривали эту тему в статье установка из tar.gz в Linux, но та статья ориентирована больше на новичков, которым нужно не столько разобраться, сколько получить готовую программу.

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

sudo apt install build-essential manpages-dev git automake autoconf

Затем вы можете проверить правильность установки и версию компилятора:

Но перед тем как переходить к самой компиляции программ рассмотрим более подробно составляющие этого процесса.

Как выполняется компиляция?

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

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

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

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

Компиляция программ Linux

Первое что нам понадобиться - это исходники самой программы. В этом примере мы будем собирать самую последнюю версию vim. Это вполне нейтральная программа, достаточно простая и нужная всем, поэтому она отлично подойдет для примера.

Получение исходников

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

Давайте загрузим сами исходники нашей программы с помощью утилиты git:

git clone https://github.com/vim/vim

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

Настройка configure

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

Если configure нет в папке с исходниками, вы можете попытаться выполнить такие скрипты чтобы его создать:

./bootstrap
$ ./autogen.sh

Также для создания этого скрипта можно воспользоваться утилитой automake:

aclocal
$ autoheader
$ automake --gnu --add-missing --copy --foreign
$ autoconf -f -Wall

Утилита automake и другие из ее набора генерируют необходимые файлы на основе файла Mackefile.am. Этот файл обязательно есть в большинстве проектов.

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

./configure --help

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

  • --prefix=PREFIX - папка для установки программы, вместо /, например, может быть /usr/local/, тогда все файлы будут распространены не по основной файловой системе, а в /usr/local;
  • --bindir=DIR - папка для размещения исполняемых файлов, должна находится в PREFIX;
  • --libdir=DIR - папка для размещения и поиска библиотек по умолчанию, тоже в PREFIX;
  • --includedir=DIR - папка для размещения man страниц;
  • --disable-возможность - отключить указанную возможность;
  • --enable-возможность - включить возможность;
  • --with-библиотека - подобно enable активирует указанную библиотеку или заголовочный файл;
  • --without-библиотека - подобное disable отключает использование библиотеки.

Вы можете выполнить configure без опций, чтобы использовать значения по умолчанию, но также можете вручную указать нужные пути. В нашем случае./configure есть, и мы можем его использовать:

Во время настройки утилита будет проверять, есть ли все необходимые библиотеки в системе, и если нет, вам придется их установить или отключить эту функцию, если это возможно. Например, может возникнуть такая ошибка: no terminal library found checking for tgetent()... configure: error: NOT FOUND!

В таком случае нам необходимо установить требуемую библиотеку. Например, программа предлагает ncurses, поэтому ставим:

sudo apt install libncurces-dev

Приставка lib всегда добавляется перед библиотеками, а -dev - означает, что нам нужна библиотека со всеми заголовочными файлами. После удовлетворения всех зависимостей настройка пройдет успешно.

Сборка программы

Когда настройка будет завершена и Makefile будет готов, вы сможете перейти непосредственно к сборке программы. На этом этапе выполняется непосредственно преобразование исходного кода в машинный. Утилита make на основе Makefile сделает все необходимые действия:

После этого программа будет установлена в указанную вами папку, и вы сможете ее использовать. Но более правильный путь - создавать пакет для установки программы, это делается с помощью утилиты checkinstall, она позволяет создавать как deb, так и rpm пакеты, поэтому может использоваться не только в Ubuntu. Вместо make install выполните:

Затем просто установите получившийся пакет с помощью dpkg:

sudo dpkg install vim.deb

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

Если вы устанавливали программу с помощью make install, то удалить ее можно выполнив в той же папке обратную команду:

sudo make uninstall

Команда удалит все файлы, которые были скопированы в файловую систему.

Выводы

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

На завершение видео о том, что такое компилятор и интерпретатор:

Одной из ключевых характеристик PHP является то, что это интерпретируемый язык программирования. С другой стороны, языки программирования наподобие C , изначально разрабатывались для компиляции. Что это значит?

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

Рабочий цикл программы

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

Когда пишете программу, вы хотите, чтобы ее инструкции работали на компьютере. Компьютер обрабатывает информацию с помощью процессора, который поэтапно выполняет инструкции, закодированные в двоичном формате. Как из выражения «a = 3; » получить закодированные инструкции, которые процессор может понять?

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

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

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

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

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

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

Интерпретация программы

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

Интерпретатор — это исполняемый файл, который поэтапно читает программу, а затем обрабатывает, сразу выполняя ее инструкции.

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

Это ломает рабочий цикл, который был приведен на диаграмме выше. Теперь у нас есть новая диаграмма:


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

Например, так работают такие языки программирования, как Python . Вы пишете программу. Затем вводите код в интерпретатор Python , и он выполняет все описанные вами шаги. В командной строке вы можете ввести примерно следующее:

C:>python myprogram.py

В этой команде Python — это исполняемый файл. Вы вводите в него все, что находится в файле myprogram.py, и он выполняет эти инструкции. Компьютер не запустит myprogram.py без Python . Это не машинный код, который понимает процессор. Можно скомпилировать программы Python в объектный или машинный код и запустить его непосредственно в процессоре. Но эта процедура включает в себя компиляцию кода и добавление в качестве ее части всего интерпретатора Python .

Природа интерпретатора

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

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

Такой код быстрее обрабатывается, и его проще написать для исполнителя (части интерпретатора, которая исполняет ), который считывает байтовый код, а не код источника.

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

У меня есть эмулятор для игровой приставки NIntendo . Когда я загружаю ROM-файл Dragon Warrior , он форматируется в машинный код, который понимает только процессор NES . Но если я создаю виртуальный процессор, который интерпретирует байтовый код во время работы на другом процессоре, я могу запустить Dragon Warrior на любой машине с эмулятором.

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

За и против

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

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

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

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

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

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

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

Это проблема для конкретных real-time приложений, таких как игры с высоким разрешением и симуляцией. Некоторые интерпретаторы содержат компоненты, которые называются just-in-time компиляторами (JIT ). Они компилируют программу непосредственно перед ее исполнением. Это специальные программы, вынесенные за рамки интерпретатора. Но поскольку процессоры становятся все более мощными, данная проблема становится менее актуальной.

Заключение

Имейте всегда в виду, что некоторые языки программирования специально предназначены для компиляции кода, например, C . В то время как другие языки всегда должны интерпретироваться, например Java .

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

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



 

Пожалуйста, поделитесь этим материалом в социальных сетях, если он оказался полезен!