Компиляция - это простой перевод с одного языка на другой. Если вы можете выразить то же самое на языке A
и языке B
, вы можете скомпилировать это, выраженное на языке A
, в то же самое на языке B
.
После того, как вы выразили свое намерение на каком-либо языке, оно выполняется путем интерпретации. Даже при использовании C или какого-либо другого компилируемого языка ваша инструкция:
- Переведено с C -> язык ассемблера
- В переводе с ассемблера -> машинный код
- Интерпретирует машина.
Компьютер на самом деле является интерпретатором очень базового языка. Поскольку он настолько прост и с ним так сложно работать, люди придумали другие языки, с которыми легче работать и которые можно легко перевести в эквивалентные операторы в машинном коде (например, C). Затем вы можете захватить фазу компиляции, выполнив перевод «на лету», как это делает JIT-компилятор, или написав свой собственный интерпретатор, который непосредственно выполняет оператор на вашем языке высокого уровня (например, LISP или Python).
Но обратите внимание, что интерпретатор - это просто ярлык для непосредственного выполнения вашего кода! Если бы вместо выполнения кода интерпретатор напечатал бы любой вызов, который он будет делать, если бы он выполнял код, у вас был бы ... компилятор. Конечно, это был бы очень глупый компилятор, и он не использовал бы большую часть имеющейся информации.
Фактические компиляторы попытаются собрать как можно больше информации из всей программы перед генерацией кода. Например, следующий код:
const bool dowork = false;
int main() {
if (dowork) {
//... lots of code go there ...
}
return 0;
}
Теоретически будет генерировать весь код внутри ветки if
. Но умный компилятор, вероятно, сочтет его недостижимым и просто проигнорирует, используя тот факт, что он знает все в программе и знает, что dowork
всегда будет false
.
Вдобавок к этому в некоторых языках есть типы, которые могут помочь в отправке вызова функций, обеспечении некоторых вещей во время компиляции и помощи при переводе в машинный код. Некоторые языки, такие как C требуют, чтобы программист объявлял тип своих переменных. Другие, такие как LISP и Python, просто предполагают тип переменной, когда она установлена, и паникуют во время выполнения, если вы пытаетесь использовать значение определенного типа, если требуется другой тип (например, если вы пишете (car 2)
в большинстве интерпретаторов Lisp, это будет вызывает некоторую ошибку, сообщающую вам, что ожидается пара). Типы могут использоваться для выделения памяти во время компиляции (например, компилятор C выделит ровно 10 * sizeof(int)
байт памяти, если требуется выделить int[10]
), но это не совсем требуется. Фактически, большинство программ на C используют указатели для хранения массивов, которые в основном являются динамическими. При работе с указателем компилятор будет генерировать / связывать код, который во время выполнения будет выполнять необходимые проверки, перераспределения и т. Д. Но суть в том, что нельзя противопоставлять динамический и скомпилированный. Интерпретаторы Python или Lisp являются скомпилированными программами, но могут работать с динамическими значениями. Фактически, сам ассемблер не является типизированным, поскольку компьютер может выполнять любую операцию с любым объектом, поскольку все, что он «видит», - это потоки битов и операции с битами. В языках более высокого уровня вводятся произвольные типы и ограничения, чтобы сделать вещи более удобочитаемыми и предотвратить совершенные безумные поступки. Но это просто помощь, а не абсолютное требование.
Теперь, когда философская тирада окончена, давайте посмотрим на ваш пример:
(setq x 60)
(setq y 40)
(+ x y)
И давайте попробуем скомпилировать это в действительную программу C. Как только это будет сделано, компиляторов C будет предостаточно, так что мы сможем перевести LISP -> C -> машинный язык или что-нибудь еще. Имейте в виду, что компиляция - это всего лишь перевод (оптимизация тоже крутая, но необязательная).
(setq
Это присвоить значение. Но мы не знаем, что на что отведено. Давай продолжим
(setq x 60)
Хорошо, мы выделяем 60 на x. 60 - это целочисленный литерал, поэтому его тип C равен int
. Поскольку нет причин предполагать, что x
принадлежит к другому типу, это эквивалентно C:
int x = 60;
Аналогично для (setq y 40)
:
int y = 40;
Теперь у нас есть:
(+ x y)
+
- это функция, которая, в зависимости от реализации, может принимать несколько типов аргументов, но мы знаем, что x
и y
являются целыми числами. Наши компиляторы знают, что существует эквивалентный оператор C, а именно:
x + y;
Так что мы просто переводим это. Наша последняя программа на C:
int x = 60;
int y = 40;
x + y;
Это совершенно правильная программа на C. Это может быть намного сложнее. Например, если x
и y
очень большие, большинство LISP не позволит им переполниться, в то время как C будет, поэтому вы можете закодировать свой компилятор так, чтобы он имел собственный целочисленный тип в виде массива целых чисел (или того, что вы сочтете подходящим). Если вы можете определять общие операции (например, +
) для этих типов, ваш новый компилятор, возможно, вместо этого переведет предыдущий код в этот:
int* x = newbigint("60");
int* y = newbigint("40");
addbigints(x, y);
С вашими функциями newbigint
и addbigints
, определенными в другом месте или сгенерированными компилятором. Он по-прежнему будет действительным C, поэтому он будет компилироваться. Фактически, ваш собственный интерпретатор, вероятно, реализован на каком-то языке более низкого уровня и уже имеет представления для объектов LISP в своей собственной реализации, поэтому он может использовать их напрямую.
Кстати, именно это и делает компилятор Cython для кода Python :)
Вы можете определять типы статически в Cython, чтобы получить дополнительную скорость / оптимизацию, но это не требуется. Cython может переводить ваш код Python непосредственно на C, а затем в машинный код.
Надеюсь, это проясняет! Помните:
- ВСЕ код интерпретируется, в конечном итоге
- Компиляторы переводят код во что-то, что легче / быстрее интерпретировать. Они часто проводят оптимизацию в процессе, но это не часть определения.
person
val
schedule
21.08.2013
a
иb
. - person   schedule 21.08.2013(let ((n 8)))
, тоn
не имеет типа, но значение8
, к которомуn
привязано в рамках этогоlet
, имеет типinteger
. - person Svante   schedule 22.08.2013