Идет мужик мимо магазина с вывеской "Коммутаторы, аккумуляторы" и читает:
– Кому таторы, а кому ляторы
В рамках подготовки к следующим выпускам назрела необходимость пояснить, как программы переводятся с языков программирования в машинный код.
Я уже упоминал, что этим занимаются трансляторы языков. Транслятор – значит переводчик. Переводит с языка на язык.
Но переводчики эти бывают разного типа, что накладывает отпечаток и на сами языки.
Интерпретатор
В переводе с английского это тоже переводчик, но особого типа. Это синхронный переводчик, который присутствует на разных деловых встречах и делает перевод синхронно с тем, как выступающий говорит. Сказали одну фразу – интерпретатор перевел. Сказали ещё одну – интерпретатор опять перевел. И так далее.
Точно так же работает и программный интерпретатор. Прочитал одну строчку программы, перевел её в машинный код, выполнил. Прочитал следующую строчку, перевёл, выполнил.
Компилятор
Это переводится как "сборщик" или "составитель". Похож на переводчика художественной литературы. Такой переводчик сначала читает книгу целиком, находит в ней взаимосвязи и отсылки к различным фактам, собирает все сведения и только потом переводит. Так же поступает и компилятор. Он собирает все данные о программе и переводит их в машинный код. После чего код выполняется целиком и непрерывно.
Плюсы и минусы
Компиляция программ очевидно выгоднее. Во-первых, машинный код исполняется максимально быстро. Во-вторых, готовой программе не нужен интерпретатор, то есть ещё одна программа. Ведь она уже и так в машинных кодах, поэтому запускаться может уже самостоятельно.
Чтобы выполнить интерпретируемую программу, вам вместе с ней нужно носить интерпретатор – ещё одну программу.
Например, если написать программу на Python, Java*, JavaScript или PHP, то чтобы отдать её кому-то, нужно вместе с ней отдать интерпретатор соответствующего языка.
Кроме того, так как интерпретатор выполняет программу строчка за строчкой, он тут же забывает о том, что выполнял. Если программа возвращается на уже исполнявшиеся строчки (например, вызывается одна и та же функция, или делается цикл), то эти строчки интерпретатор переводит повторно. Это, конечно, приводит к лишним затратам времени и программа выполняется медленнее.
Но у интерпретаторов есть и плюс. Каждый раз, когда вы делаете какое-то изменение в программе, вы можете просто перезапустить её и сразу увидеть это изменение. Если программа компилируется, то каждый раз после изменения её нужно сначала скомпилировать, и только потом запустить. Хотя сама компиляция длится недолго (если это не огромный проект), даже лишние несколько секунд всё-таки мешают, особенно в процессе обучения.
И еще один плюс – интерпертируемая программа может работать везде, где есть интерпретатор. Например, программа, написанная на Питоне, может работать под Windows или под MacOS или под Android, лишь бы там был интерпретатор Питона.
А скомпилированная программа переведена уже в конкретные машинные коды под конкретную систему. Поэтому программа, скомпилированная для Windows, не будет работать в Linux, MacOS и др. Также программа, скомпилированная для процессора Intel, не будет работать на процессоре "Эльбрус" или PDP-11, потому что у них разные машинные коды. Нужно для каждой системы и архитектуры процессора собирать свой вариант программы. При этом, возможно, придется менять даже её исходный код.
*Байт-код
Первоначально интерпретаторы были однозначно медленнее компиляторов, в десятки, если не сотни раз. Это даже не вызывало вопроса. Интерпретатор – плохо. Постепенно разрыв стал сокращаться. Разработчики интерпретаторов шли на всевозможные ухищрения, чтобы их ускорить.
Техник для этого придумали много, и трудно даже описать, где какая используется, потому что всё время появляются новые доработки. Начали с очевидного: перевел строчку – не забывай её сразу, сохрани где-нибудь, вдруг понадобится. Если программа снова вернулась на эту строчку – вот она, уже переведенная, отлично. Язык Java вообще применил гибридный метод – программа на самом деле компилируется, но не в машинный код, а в специальный байт-код, который выполняется в виртуальной машине. То есть интерпретатор Java – это как бы отдельный компьютер внутри компьютера, который исполняет программы в своих собственных машинных кодах.
Компилятору Java не нужно переводить программы в разные машинные коды для разных систем – он переводит их всегда в один и тот же код для своей собственной виртуальной машины, которая на всех системах работает одинаково. Но так как этот код – не нативный, т.е. не "родной" код для процессора, то он по-прежнему интерпретируется виртуальной машиной, которая переводит его в нативный код. Но так как этот код уже оптимизирован для интерпретации (он почти машинный, "полуфабрикат"), то и интерпретатор работает гораздо быстрее.
Я думаю, все интерпретаторы сейчас так или иначе используют байт-код. Кроме того, интерпретируемые программы часто бывает можно скомпилировать в нативный код. Правда, скорее всего это будет всё тот же интерпретатор, который будет содержаться внутри скомпилированной программы и по-прежнему интерпретировать её. И размер программы, конечно, станет существенно больше из-за этого.
В целом, как мне кажется, сейчас компиляторы востребованы только для программ, где важна скорость и размер (тяжелые игры, операционные системы, системные приложения, базы данных, драйвера, обработчики событий реального времени, системы с очень маленьким объемом памяти). Интерпретаторы и виртуальные машины достигли уже неплохих скоростей для большинства задач и поддерживаются "из коробки" на многих платформах. Скажем, тот же JavaScript работает в любом современном браузере.
Так как современная разработка ориентируется в большой степени на универсальные (в том числе мобильные) приложения, то требуется писать программы, которые работают везде. Именно это приводит к популярности интерпретируемых языков или языков с виртуальными машинами, а также кросс-компиляторов – компиляторов, которые переводят программы не в машинный код, а с одного языка на другой.