Интерпретатор (вычисления)

редактировать

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

  1. Анализировать исходный код и выполнять его поведение напрямую;
  2. Преобразовать исходный код в некоторый эффективный промежуточное представление и немедленно выполнить это;
  3. Явно выполнить сохраненный предварительно скомпилированный код, созданный компилятором, который является частью системы интерпретатора.

Ранние версии программирования на Лиспе. язык и диалекты BASIC мини-компьютера и микрокомпьютера будут примерами первого типа. Perl, Python, MATLAB и Ruby - примеры второго, а UCSD Pascal - пример третьего типа. Исходные программы компилируются заранее и сохраняются как машинно-независимый код, который затем связывается во время выполнения и выполняется интерпретатором и / или компилятором (для систем JIT ). Некоторые системы, такие как Smalltalk и современные версии BASIC и Java, также могут сочетать два и три. Интерпретаторы различных типов также были созданы для многих языков, традиционно связанных с компиляцией, таких как Algol, Fortran, Cobol, C и C ++.

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

Содержание
  • 1 История
  • 2 Компиляторы и интерпретаторы
    • 2.1 Цикл разработки
    • 2.2 Распространение
    • 2.3 Эффективность
    • 2.4 Регрессия
  • 3 Варианты
    • 3.1 Интерпретаторы байт-кода
    • 3.2 Потоковые интерпретаторы кода
    • 3.3 Интерпретаторы абстрактного синтаксического дерева
    • 3.4 Своевременная компиляция
    • 3.5 Самоинтерпретатор
    • 3.6 Микрокод
    • 3.7 Компьютерный процессор
  • 4 Приложения
  • 5 См. Также
  • 6 Примечания и ссылки
  • 7 Внешние ссылки
История

Интерпретаторы использовались еще в 1952 году, чтобы упростить программирование в рамках ограничений компьютеров того времени (например, нехватка памяти для программ пробел или отсутствие встроенной поддержки чисел с плавающей запятой). Интерпретаторы также использовались для перевода между низкоуровневыми машинными языками, что позволяло писать код для машин, которые все еще находились в стадии разработки, и тестировать их на уже существующих компьютерах. Первым интерпретируемым языком высокого уровня был Lisp. Лисп был впервые реализован в 1958 году Стивом Расселом на компьютере IBM 704. Рассел прочитал статью Джона Маккарти и понял (к удивлению Маккарти), что функция eval Лиспа может быть реализована в машинном коде. Результатом стал работающий интерпретатор Лиспа, который можно было использовать для запуска программ Лиспа, или, точнее, «оценки выражений Лиспа».

Компиляторы против интерпретаторов
Иллюстрация процесса связывания. Объектные файлы и статические библиотеки собираются в новую библиотеку или исполняемый файл

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

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

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

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

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

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

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

Цикл разработки

Во время цикл разработки программного обеспечения, программисты часто вносят изменения в исходный код. При использовании компилятора каждый раз, когда в исходный код вносятся изменения, они должны ждать, пока компилятор переведет измененные исходные файлы и свяжет все файлы двоичного кода вместе, прежде чем программа сможет быть выполнена. Чем больше программа, тем дольше ожидание. Напротив, программист, использующий интерпретатор, ожидает намного меньше, поскольку интерпретатору обычно просто нужно перевести код, над которым работает, в промежуточное представление (или не переводить его вообще), что требует гораздо меньше времени, прежде чем изменения могут быть проверено. Эффекты очевидны после сохранения исходного кода и перезагрузки программы. Скомпилированный код, как правило, труднее отлаживать, поскольку редактирование, компиляция и компоновка - это последовательные процессы, которые должны выполняться в правильной последовательности с правильным набором команд. По этой причине многие компиляторы также имеют вспомогательное средство управления, известное как файл и программа Make. В файле Make file перечислены командные строки компилятора и компоновщика и файлы исходного кода программы, но он может принимать простой ввод из меню командной строки (например, «Make 3»), который выбирает третью группу (набор) инструкций, затем выдает команды компилятору и компоновщик, загружающий указанные файлы исходного кода.

Распространение

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

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

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

Эффективность

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

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

Существуют различные компромиссы между использованием интерпретатора и скоростью выполнения при использовании компилятора. Некоторые системы (например, некоторые Lisps ) позволяют интерпретируемому и скомпилированному коду вызывать друг друга и совместно использовать переменные. Это означает, что после того, как подпрограмма была протестирована и отлажена в интерпретаторе, ее можно скомпилировать и, таким образом, получить выгоду от более быстрого выполнения, в то время как другие подпрограммы разрабатываются. Многие интерпретаторы не выполняют исходный код в его нынешнем виде, а преобразуют его в более компактную внутреннюю форму. Многие интерпретаторы BASIC заменяют ключевые слова одиночными байтами токенами, которые можно использовать для поиска инструкции в таблице переходов. Некоторые интерпретаторы, такие как интерпретатор PBASIC, достигают еще более высоких уровней сжатия программ за счет использования побитовой, а не побайтно-ориентированной структуры памяти программ, где токены команд занимают, возможно, 5 бит, номинально »16 -bit "константы хранятся в коде переменной длины, требующем 3, 6, 10 или 18 битов, а операнды адреса включают" битовое смещение ". Многие интерпретаторы BASIC могут сохранять и считывать собственное токенизированное внутреннее представление.

Toy C интерпретатор выражений
// типы данных для абстрактного синтаксического дерева enum _kind {kVar, kConst, kSum, kDiff, kMult, kDiv, kPlus, kMinus, kNot}; struct _variable {int * память; }; struct _constant {значение int; }; struct _unaryOperation {struct _node * right; }; struct _binaryOperation {struct _node * left, * right; }; struct _node {enum _kind kind; union _expression {struct _variable переменная; struct _constant constant; struct _binaryOperation binary; struct _unaryOperation unary; } e; }; // процедура интерпретатора int executeIntExpression (const struct _node * n) {int leftValue, rightValue; переключатель (n->kind) {case kVar: return * n->e.variable.memory; case kConst: return n->e.constant.value; case kSum: case kDiff: case kMult: case kDiv: leftValue = executeIntExpression (n->e.binary.left); rightValue = executeIntExpression (n->e.binary.right); переключатель (n->вид) {case kSum: return leftValue + rightValue; case kDiff: return leftValue - rightValue; case kMult: return leftValue * rightValue; case kDiv: if (rightValue == 0) исключение («деление на ноль»); // не возвращает return leftValue / rightValue; } case kPlus: case kMinus: case kNot: rightValue = executeIntExpression (n->e.unary.right); переключатель (n->вид) {case kPlus: return + rightValue; case kMinus: return - rightValue; case kNot: return! rightValue; } по умолчанию: исключение («внутренняя ошибка: недопустимый вид выражения»); }}

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

Регрессия

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

Варианты

Интерпретаторы байт-кода

Существует целый спектр возможностей между интерпретация и компиляция, в зависимости от объема анализа, выполненного перед выполнением программы. Например, Emacs Lisp скомпилирован в байт-код, который представляет собой сильно сжатое и оптимизированное представление исходного кода Lisp, но не является машинным кодом (и поэтому не привязан к какому-либо конкретному оборудованию). Этот "скомпилированный" код затем интерпретируется интерпретатором байт-кода (сам написан на C ). Скомпилированный код в этом случае представляет собой машинный код для виртуальной машины , который реализован не аппаратно, а в интерпретаторе байт-кода. Такие компиляторы-интерпретаторы иногда также называют компиляторами. В интерпретаторе байт-кода каждая инструкция начинается с байта, поэтому интерпретаторы байт-кода имеют до 256 инструкций, хотя не все могут использоваться. Некоторые байт-коды могут занимать несколько байтов и могут быть произвольно сложными.

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

Интерпретаторы последовательного кода

Многопоточные интерпретаторы кода похожи на интерпретаторы байт-кода, но вместо байтов они используют указатели. Каждая «инструкция» - это слово, указывающее на функцию или последовательность инструкций, за которым, возможно, следует параметр. Интерпретатор многопоточного кода либо зацикливает выборку инструкций и вызов функций, на которые они указывают, либо выбирает первую инструкцию и переходит к ней, и каждая последовательность инструкций заканчивается выборкой и переходом к следующей инструкции. В отличие от байт-кода нет эффективного ограничения на количество различных инструкций, кроме доступной памяти и адресного пространства. Классическим примером многопоточного кода является код Forth, используемый в системах Open Firmware : исходный язык компилируется в «F-код» (байт-код), который затем интерпретируется виртуальная машина.

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

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

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

Компиляция точно в срок

Дальнейшее стирание различия между интерпретаторами, интерпретаторами байт-кода и компиляцией - это JIT-компиляция, метод, при котором компилируется промежуточное представление на собственный машинный код во время выполнения. Это обеспечивает эффективность выполнения собственного кода за счет времени запуска и увеличения использования памяти при первой компиляции байт-кода или AST. Самый ранний опубликованный JIT-компилятор обычно приписывается работе над LISP Джоном Маккарти в 1960 году. Адаптивная оптимизация - это дополнительный метод, в котором интерпретатор профилирует выполняющуюся программу и компилирует свои наиболее часто выполняемые части в собственный код. Последнему методу уже несколько десятилетий, и он появился в таких языках, как Smalltalk в 1980-х.

Компиляция точно в срок привлекла основное внимание разработчиков языков в последние годы, с Java, .NET Framework, самые современные реализации JavaScript и Matlab, теперь включая JIT.

Самоинтерпретатор

Самоинтерпретатор - это интерпретатор языка программирования, написанный на языке программирования, который может интерпретировать сам себя; Примером может служить интерпретатор BASIC, написанный на BASIC. Самоинтерпретаторы связаны с компиляторами с собственным хостом.

. Если для интерпретируемого языка не существует компилятора, для создания самоинтерпретатора требуется реализация языка на основном языке (который может быть другой язык программирования или ассемблер ). При наличии такого первого интерпретатора система загружается и новые версии интерпретатора могут быть разработаны на самом языке. Таким образом, Дональд Кнут разработал интерпретатор TANGLE для языка WEB промышленного стандарта TeX системы набора.

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

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

Некоторые языки имеют элегантный самоинтерпретатор, например, Lisp или Prolog. Много исследований самоинтерпретаторов (особенно рефлексивных интерпретаторов) было проведено на языке программирования Scheme, диалекте Lisp. В целом, однако, любой полный по Тьюрингу язык позволяет писать свой собственный интерпретатор. Лисп является таким языком, потому что программы на Лиспе представляют собой списки символов и другие списки. XSLT является таким языком, потому что программы XSLT написаны на XML. Поддомен метапрограммирования - это написание предметно-ориентированных языков (DSL).

Клайв Гиффорд ввел критерий качества самоинтерпретатора (собственное отношение), предел отношения между компьютерным временем, затраченным на выполнение стека из N самоинтерпретаторов, и временем, затраченным на запуск стека из N - 1 самосогласования. -интерпретатор, поскольку N стремится к бесконечности. Это значение не зависит от запущенной программы.

Книга Структура и интерпретация компьютерных программ представляет примеры мета-круговой интерпретации для Scheme и ее диалектов. Другими примерами языков с самоинтерпретатором являются Forth и Pascal.

Microcode

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

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

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

Компьютерный процессор

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

Приложения
  • Интерпретаторы часто используются для выполнения командных языков и связующих языков, поскольку каждый оператор, выполняемый на командном языке, обычно является вызовом сложной процедуры, такой как в качестве редактора или компилятора.
  • Самомодифицирующийся код можно легко реализовать на интерпретируемом языке. Это относится к истокам интерпретации в Лиспе и исследованиям искусственного интеллекта.
  • Виртуализация. Машинный код, предназначенный для аппаратной архитектуры, можно запускать с помощью виртуальной машины. Это часто используется, когда намеченная архитектура недоступна, или, среди прочего, для запуска нескольких копий.
  • «Песочница» : хотя некоторые типы «песочниц» полагаются на средства защиты операционной системы, часто используется интерпретатор или виртуальная машина. Фактическая архитектура оборудования и изначально предполагаемая архитектура оборудования могут совпадать, а могут и не совпадать. Это может показаться бессмысленным, за исключением того, что песочницы не обязаны фактически выполнять все инструкции исходного кода, которые они обрабатывают. В частности, он может отказаться выполнять код, который нарушает любые ограничения безопасности, в которых он работает.
  • Эмуляторы для запуска компьютерного программного обеспечения, написанного для устаревшего и недоступного оборудования на более современном оборудовании.
См. Также
Примечания и ссылки
Внешние ссылки
Последняя правка сделана 2021-05-24 05:04:53
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте