Производит ли переводчик машинный код?

42

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

У меня есть язык под названием "Foobish", и его ключевые слова

<OUTPUT> 'TEXT', <Number_of_Repeats>;

Поэтому, если я хочу вывести на консоль 10 раз, я бы написал

OUTPUT 'Hello World', 10;

Привет World.foobish-файл.

Теперь я пишу переводчик на языке по своему выбору - C # в этом случае:

using System;

namespace FoobishInterpreter
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            analyseAndTokenize(Hello World.foobish-file)//Pseudocode
            int repeats = Token[1];
            string outputString = Token[0];
            for (var i = 0; i < repeats; i++)
            {
                Console.WriteLine(outputString);
            }
        }
    }
}

На очень простом уровне интерпретатора интерпретатор анализирует файл сценария и т. Д. И исполняет язык foobish в соответствии с реализацией интерпретатора.

Будет ли компилятор создавать машинный язык, который работает непосредственно на физическом оборудовании?

Таким образом, интерпретатор не создает машинный язык, но компилятор делает это для его ввода?

Есть ли у меня какие-либо недоразумения в основном, как работают компиляторы и интерпретаторы?

GrayFox
источник
21
Как вы думаете, что делает C # "компилятор"? Как подсказка, он не производит машинный код.
Филипп Кендалл
3
Компилятор Java создает код для JVM. Таким образом, целевой машиной компилятора может быть виртуальная машина, которая не выполняется непосредственно аппаратным обеспечением. Основное различие между интерпретатором и компилятором заключается в том, что компилятор сначала проверяет и переводит весь исходный код на целевой машинный язык. Этот скомпилированный код затем выполняется на машине, для которой он предназначен. С другой стороны, переводчик будет переводить и выполнять фрагменты вашей программы на лету.
Джорджио
@ Джорджио: Вы имеете в виду, как JIT?
Роберт Харви
2
@RobertHarvey: я имел в виду компилятор Java (javac): насколько я знаю, он генерирует байт-код для JVM. И, опять же, AFAIK, JIT позже (во время выполнения) компилирует некоторый байт-код, который очень часто используется на родном машинном языке.
Джорджио
4
компилятор означает перевод. Он может испускать все виды языков: c, ассемблер, javascript, машинный код.
Эсбен Сков Педерсен

Ответы:

77

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

Но сегодня существует множество вариантов использования «компилятора» и «интерпретатора». Например, VB6 «компилируется» в байтовый код (форма языка промежуточного уровня ), который затем «интерпретируется» средой выполнения VB. Аналогичный процесс происходит в C #, который создает CIL, который затем выполняется компилятором Just-In-Time (JIT), который в прежние времена считался интерпретатором. Вы можете «заморозить» вывод JIT в настоящий двоичный исполняемый файл, используя NGen.exe , продукт которого был бы результатом компиляции в старые времена.

Таким образом, ответ на ваш вопрос не так прост, как это было раньше.

Дальнейшее чтение
компиляторов и переводчиков в Википедии

Роберт Харви
источник
6
@ Джорджио: Большинство переводчиков в настоящее время на самом деле не выполняют исходный код, а скорее выводят AST или что-то подобное. Компиляторы имеют похожий процесс. Различие не столь четкое, как вы думаете.
Роберт Харви
5
«Вы можете« заморозить »вывод JIT в фактический двоичный исполняемый файл, используя NGen.exe, продукт которого был бы результатом компиляции в старые времена.»: Но это все еще сегодня результат компилятора (а именно, компилятор точно в срок). Неважно, когда компилятор запущен, но что он делает. Компилятор принимает в качестве входных данных представление фрагмента кода и выводит новое представление. Интерпретатор выведет результат выполнения этого фрагмента кода. Это два разных процесса, независимо от того, как вы их смешиваете и когда что выполняете.
Джорджио
4
«Компилятор» - это просто термин, который они выбрали для присоединения к GCC. Они предпочли не называть NGen компилятором, даже если он генерирует машинный код, предпочитая вместо этого присоединять этот термин к предыдущему шагу, который можно назвать интерпретатором, даже если он генерирует машинный код (некоторые интерпретаторы также делают это). Моя точка зрения заключается в том, что в настоящее время не существует обязательного принципа, который вы могли бы использовать, чтобы окончательно называть что-то компилятором или интерпретатором, кроме «это то, что они всегда называли».
Роберт Харви
4
Как показывает мое очень ограниченное понимание, в наши дни процессоры x86 в любом случае на полпути являются аппаратными JIT-движками, а сборка имеет постоянно исчезающую связь с тем, что именно исполняется.
Леушенко
4
@RobertHarvey, хотя я согласен с тем, что нет четкой разделительной линии между методами, используемыми в интерпретаторе и компиляторе, есть довольно четкое разделение в функции: если результатом выполнения данного инструмента с кодом программы в качестве ввода является выполнение этого программа, инструмент переводчик. Если результатом является вывод перевода программы в менее абстрактную форму, это компилятор. Если результатом является перевод в более абстрактную форму, то это декомпилятор. Однако случаи, когда более одного из этих результатов неоднозначны.
Жюль
34

Краткое изложение, которое я даю ниже, основано на «Компиляторах, принципах, методах и инструментах», Aho, Lam, Sethi, Ullman (Pearson International Edition, 2007), страницы 1, 2, с добавлением некоторых моих собственных идей.

Два основных механизма обработки программы - это компиляция и интерпретация .

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

source program --> | compiler | --> target program

Если целевой язык - машинный код, он может быть выполнен непосредственно на некотором процессоре:

input --> | target program | --> output

Компиляция включает в себя сканирование и перевод всей входной программы (или модуля) и не требует ее выполнения.

Интерпретация принимает в качестве входных данных исходную программу и ее входные данные и производит выходные данные исходной программы

source program, input --> | interpreter | --> output

Интерпретация обычно включает обработку (анализ и выполнение) программы по одному утверждению за раз.

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

source program --> | translator | --> intermediate program

выходные данные этого шага затем выполняются (интерпретируются) виртуальной машиной:

intermediate program + input --> | virtual machine | --> output

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

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

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

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

Чтобы ответить на первоначальные вопросы.

Компилятор будет создавать машинный язык, который работает на физическом оборудовании напрямую?

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

То есть интерпретатор не создает машинный язык, а компилятор делает это для его ввода?

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

Джорджио
источник
7
Другими словами: интерпретатор берет программу P и выдает ее вывод O, компилятор берет P и выдает программу P ', которая выдает O; интерпретаторы часто включают в себя компоненты, которые являются компиляторами (например, для байт-кода, промежуточного представления или машинных инструкций JIT), а также компилятор может включать в себя интерпретатор (например, для оценки вычислений во время компиляции).
Джон Пурди
«Компилятор может включать интерпретатор (например, для оценки вычислений во время компиляции)»: Хороший вопрос. Я предполагаю, что макросы Lisp и шаблоны C ++ могут быть предварительно обработаны таким образом.
Джорджио
Еще проще, препроцессор C компилирует исходный код C с директивами CPP в простой C и включает интерпретатор для логических выражений, таких как defined A && !defined B.
Джон Пурди
@JonPurdy Я бы с этим согласился, но я бы также добавил класс, «традиционные интерпретаторы», которые не используют промежуточные представления помимо, возможно, токенизированной версии источника. Примерами могут служить оболочки, много бейсиков, классический Lisp, Tcl до 8.0 и bc.
Хоббс
1
@naxa - см. ответ Лоуренса и комментарии Пола Дрейпера о типах компилятора. Ассемблер - это особый вид компилятора, в котором (1) язык вывода предназначен для непосредственного выполнения машиной или виртуальной машиной и (2) существует очень простое взаимно-однозначное соответствие между инструкциями ввода и инструкциями вывода.
Жюль
22

Компилятор создаст машинный язык

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

Компилятор может компилировать из языка высокого уровня в другой язык высокого уровня (например, GWT, который компилирует Java в ECMAScript), из языка высокого уровня в язык низкого уровня (например, Gambit, который компилирует Scheme в C), от языка высокого уровня до машинного кода (например, GCJ, который компилирует Java в нативный код), от языка низкого уровня до языка высокого уровня (например, Clue, который компилирует C в Java, Lua, Perl, ECMAScript и Common Lisp), от низкоуровневого языка к другому низкоуровневому языку (например, Android SDK, который компилирует байт-код JVML в байт-код Dalvik), от низкоуровневого языка до машинного кода (например, компилятор C1X, который является частью HotSpot, который компилирует байт-код JVML в машинный код), машинный код - в язык высокого уровня (любой так называемый «декомпилятор», также Emscripten, который компилирует машинный код LLVM в ECMAScript),машинный код на низкоуровневый язык (например, JIT-компилятор в JPC, который компилирует собственный код x86 в байт-код JVML) и собственный код в собственный код (например, JIT-компилятор в PearPC, который компилирует собственный код PowerPC в собственный код x86).

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

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

  • если язык A воспринимается примерно на том же уровне абстракции, что и язык B , компилятор может называться транспилером (например, Ruby-to-ECMAScript-транспортер или ECMAScript2015-ECMAScript5-транспортер)
  • если язык A воспринимается как находящийся на более низком уровне абстракции, чем язык B , компилятор может называться декомпилятором (например, x86-машинный код в C-декомпилятор)
  • если язык A == язык B , компилятор можно назвать оптимизатором , обфускатором или минификатором (в зависимости от конкретной функции компилятора)

который работает на физическом оборудовании напрямую?

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

То есть интерпретатор не создает машинный язык, а компилятор делает это для его ввода?

Переводчик ничего не производит. Он просто запускает программу.

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


Вы также можете быть заинтересованы в:

Йорг Миттаг
источник
16

Я думаю, что вы должны полностью отказаться от понятия «компилятор против интерпретатора», потому что это ложная дихотомия.

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

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

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

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

Примеры схем реализации включают в себя:

  • Компилятор переменного тока, который преобразует C в машинный код x86, и процессор x86, который выполняет этот код.
  • Компилятор переменного тока, который преобразует C в LLVM IR, бэкэнд-компилятор LLVM, преобразующий LLVM IR в машинный код x86, и процессор x86, который выполняет этот код.
  • Компилятор AC, который преобразует C в IR LLVM, и интерпретатор LLVM, который выполняет IR LLVM.
  • Компилятор Java, который преобразует Java в байт-код JVM, и JRE с интерпретатором, который выполняет этот код.
  • Компилятор Java, который преобразует Java в байт-код JVM, и JRE с интерпретатором, который выполняет некоторые части этого кода, и компилятором, который преобразует другие части этого кода в машинный код x86, и процессором x86, который выполняет этот код.
  • Компилятор Java, который преобразует Java в байт-код JVM, и процессор ARM, который выполняет этот код.
  • Компилятор AC #, который преобразует C # в CIL, CLR с компилятором, который преобразует CIL в машинный код x86, и процессор x86, который выполняет этот код.
  • Интерпретатор Ruby, который выполняет Ruby.
  • Среда Ruby с интерпретатором, выполняющим Ruby, и компилятором, преобразующим Ruby в машинный код x86, и процессором x86, выполняющим этот код.

...и так далее.

Теодорос Чатзигианнакис
источник
+1 за указание на то, что даже кодировки, которые были разработаны для промежуточного представления (например, байт-код Java), могут иметь аппаратные реализации.
Жюль
7

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

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

Переводчик будет делать то, что должна делать ваша программа.

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

Корт Аммон
источник
5

Вот простая концептуальная неоднозначность между компиляторами и интерпретаторами.

Рассмотрим 3 языка: язык программирования , P (на чем написана программа); язык домена , D (для того, что происходит с запущенной программой); и целевой язык, T (некоторый третий язык).

Концептуально,

  • компилятор переводит P в T , так что можно оценить T (D); в то время как

  • интерпретатор оценивает P (D) непосредственно.

Лоренс
источник
1
Большинство современных переводчиков на самом деле не оценивают исходный язык напрямую, а скорее являются промежуточным представлением исходного языка.
Роберт Харви
4
@RobertHarvey Это не меняет концептуального различия между терминами.
Лоуренс
1
То, что вы действительно называете интерпретатором, - это та часть, которая оценивает промежуточное представление. Часть, которая создает промежуточное представление , по вашему определению, является компилятором .
Роберт Харви
6
@RobertHarvey Не совсем. Термины зависят от уровня абстракции, на которой вы работаете. Если вы посмотрите внизу, инструмент может делать что угодно. По аналогии, скажем, вы отправляетесь в чужую страну и приводите с собой двуязычного друга Боба. Если вы общаетесь с местными жителями, разговаривая с Бобом, который, в свою очередь, общается с местными жителями, Боб выступает в роли переводчика для вас (даже если он набрасывается на их языке, прежде чем говорить). Если вы спрашиваете у Боба фразы, а Боб пишет их на иностранном языке, и вы общаетесь с местными жителями, ссылаясь на эти записи (не Боб), Боб выступает в роли компилятора для вас.
Лоуренс
1
Отличный ответ. Стоит отметить: в настоящее время вы можете услышать «транспортер». Это компилятор, где P и T - одинаковые уровни абстракции, для некоторого определения аналога. (Например, транспортер ES5 - ES6.)
Пол Дрейпер