В этом уроке мы разберемся с особенностями компилируемых и интерпретируемых языков программирования и с тем, как компьютер выполняет программы.
Как компьютер выполняет программы?
Все множество языков программирования, условно, можно разделить на две большие группы: компилируемые и интерпретируемые.
Для начала разберемся с тем, как компьютер выполняет программы. Почти все современные компьютеры построены по принципу архитектуры фон Неймана. Суть идеи можно представить в виде следующей схемы:
Процессор извлекает из памяти команды и данные, и выполняет соответствующие операции, также он может принимать команды с устройства ввода, а результат отправлять на устройства вывода. В реальности все гораздо сложнее, но для предварительного знакомства этого достаточно.
Важно, что в памяти лежат как программы (в виде команд), которые будут выполняться на процессоре, так и данные, с которыми эти команды будут работать. Структурно они не отличны друг от друга, и то и другое является набором нулей и единиц. Таким образом, при необходимости, мы можем воспринимать программы как данные для других программ, а то, что раньше было данными, пытаться выполнить как программы.
Оставим пока в стороне данные и сосредоточимся на программах.
Как мы уже сказали, если посмотреть на то, как хранится программа, то это просто набор нулей и единиц. Чтобы процессор мог её выполнить, он должен интерпретировать содержимое памяти как набор команд. Например, последовательность 0001000, считанная из памяти, может интерпретироваться как команда “Загрузить”, а 00100001, как “Вычесть” и т. д. Последовательность таких инструкций для процессора, сохраненная где-то в памяти, называется машинным кодом. Процессор может выполнять только машинный код. При этом для разных семейств процессоров он свой. Поэтому программу, которую может выполнить процессор на смартфоне, не сможет напрямую выполнить процессор на ноутбуке. Здесь, опять-таки, много нюансов, мы сейчас рассматриваем ситуацию в “чистом” виде – на уровне машинных кодов.
Чтобы запустить какую-то программу на нашем компьютере, мы находим соответствующий исполняемый файл, который, например, в Windows, будет иметь расширение *.exe, и два раза щелкаем по нему мышкой. В этот момент операционная система, “глядя” на расширение, понимает, что этот файл нужно выполнить как программу и инициирует соответствующий процесс.
Мы немного разобрались в том, как примерно устроен и работает компьютер с точки зрения выполнения программы, хранимой в машинном коде. Теперь нужно понять как программа, написанная на каком-либо языке программирования высокого уровня (C, C++, Java, C#, Python), преобразуется в машинный код и выполняется.
На сегодняшний день существует три основных подхода к тому, как это делать:
- Компиляция в машинный код.
- Компиляция в байт-код, выполняемый на виртуальной машине.
- Интерпретация.
Давайте разберемся, в чем особенность каждого из этих подходов.
Компилируемые и интерпретируемые языки программирования
Как мы сказали на предыдущем степе, существует три основных подхода к тому, как преобразовать программу с языка высокого уровня в машинный код и выполнить ее на процессоре:
- Компиляция в машинный код.
- Компиляция в байт-код, выполняемый на виртуальной машине.
- Интерпретация.
Компиляция в машинный код
Такой подход используется, например, в таких языках, как Assembler/C/C++. В этом случае программа, написанная на языке программирования высокого уровня, транслируется в код на языке ассемблера для конкретного процессора, а из него в машинный код. То есть получаемый в итоге файл – это набор инструкций для конкретного процессора с дополнительной информацией в виде заголовков и т. п., которая нужна для исполнения.
Всю работу по превращению программы с языка программирования высокого уровня в машинный код делает компилятор. А саму процедуру называют компиляцией.
Результат упаковывается в исполняемый файл, который операционная система может выполнить на конкретном аппаратном обеспечении.
Как уже было сказано, такой вариант используется для языков Assembler/C/C++. Получаемое в таком случае решение, как правило, более производительное, если сравнивать с эквивалентной по функционалу реализацией для виртуальной машины или интерпретатора.
Компиляция в байт-код, выполняемый на виртуальной машине
Следующий вариант – это компиляция не в машинный код конкретного процессора, а в так называемый байт-код для виртуальной машины. По такому пути пошли Java и C#. Суть идеи в том, что мы можем придумать свой процессор с определенным набором команд, и реализовать его в виде программы для реальных процессоров. То есть мы получаем виртуальный “процессор”, который исполняется как программа на реальном (аппаратном) процессоре. Какие преимущества это нам дает? Во-первых – это возможность написать и собрать исполняемый файл один раз и запускать его в разных операционных системах и на различном оборудовании без перекомпиляции. Во-вторых – унифицировать способы работы с оборудованием, сетью и т. п. без привязки к особенностям операционной системы и окружения, в котором будет запускаться наше программное обеспечение.
В этом варианте также присутствует процесс компиляции, только теперь мы переводим не в машинный код процессора (аппаратного), а в байт-код виртуальной машины.
Процесс выполнения чуть более сложный: байт-код выполняется в виртуальной машине, которая его интерпретирует и компилирует “на лету” в машинный код для процессора, на котором она выполняется – это называется Just-in-Time (JIT) компиляция. Полученный машинный код выполняется на аппаратном процессоре.
Интерпретация
Если язык программирования интерпретируемый, то у него нет такой стадии, как компиляция. В этом случае исходный код и является продуктом, который будет исполняться. Примерами таких языков являются Python и JavaScript.
После того как вы написали программу, например, на Python, она сразу же готова к исполнению.
Для исполнения такого типа программ нужен специальный инструмент, который называется интерпретатор. Он принимает исходный код программы и выполняет его. Интерпретатор можно воспринимать как программный компьютер, на котором выполняется не машинный код, а тот, что вы написали. Идея, аналогичная варианту с виртуальной машиной, только здесь она выполняет не байт-код, а код на языке высокого уровня.
Интерпретатор языка Python вы можете установить себе на компьютер как отдельное приложение, а интерпретатор JavaScript встроен в браузер (есть ещё Node.js – платформа, используя которую можно запускать JavaScript без браузера, как обычное приложение).
Если говорить про C#, то он относится к компилируемым языкам программирования с исполнением на виртуальной машине.
Если Вы хотите больше узнать про язык C#, приглашаем Вас на наш курс “C#. Базовый уровень“.