Общая презентация
Компилятор выполняет следующие операции: лексический анализ , предварительная обработка ( предварительная обработка ), синтаксический анализ ( синтаксический анализ ), семантический анализ и оптимизация генерации кода . За компиляцией часто следует этап редактирования ссылки для создания исполняемого файла. Когда скомпилированная программа (объектный код) выполняется на компьютере, процессор или операционная система которого отличается от процессора или операционной системы компилятора, это называется кросс-компиляцией .
Есть два варианта компиляции:
- Опережая время (AOT), когда вы должны скомпилировать программу перед запуском приложения: это традиционная ситуация.
- Компиляция на лету ( точно в срок , сокращенно JIT): эта опция появилась в 1980-х годах (например, с Tcl / Tk ).
Определение порядка байтов
Порядок байтов в конкретной машине можно определить с помощью программы на языке Си (testbyteorder.c):
#include <stdio.h> unsigned short x = 1; /* 0x0001 */ int main(void) { printf("%s\n", *((unsigned char *) &x) == ? "big-endian" "little-endian"); return ; }
- Вывод данной программы осмыслен только на платформах, где размер типа unsigned short больше, чем размер типа unsigned char. Это заведомо верно на подавляющем большинстве компьютеров, так как они имеют 8-разрядный байт. Однако существуют и аппаратные платформы, в которых размер байта равен размеру слова (или, в терминах языка C: sizeof(char) == sizeof(short)). Например, в суперкомпьютерах Cray.
Результаты запуска на big-endian машине (SPARC):
$ uname -m sparc64 $ gcc -o testbyteorder testbyteorder.c $ ./testbyteorder big-endian
Результаты запуска на little-endian машине (x86):
$ uname -m i386 $ gcc -o testbyteorder testbyteorder.c $ ./testbyteorder little-endian
Кросс-компиляция
Кросс — компилятор работает в одной среде , но создает объектный код для другого. Кросс-компиляторы используются для встраиваемой разработки, когда целевой компьютер имеет ограниченные возможности.
Ранним примером кросс-компиляции была AIMICO, где программа FLOW-MATIC на UNIVAC II использовалась для создания языка ассемблера для IBM 705 , который затем был собран на компьютере IBM.
68C алгол компилятор ZCODE выход, который может быть затем либо компилируется в локальный код с помощью машинного ZCODE переводчика или запустить интерпретирован. ZCODE — это промежуточный язык на основе регистров. Эта способность интерпретировать или компилировать ZCODE способствовала переносу ALGOL 68C на множество различных компьютерных платформ.
Языки описания грамматики
Джон Бэкус предложил «металингвистические формулы» для описания синтаксиса нового языка программирования IAL, известного сегодня как АЛГОЛ 58 (1959). Работа Бэкуса была основана на канонической системе Поста, разработанной Эмилем Постом .
Дальнейшее развитие АЛГОЛА привело к АЛГОЛу 60 ; в своем отчете (1963) Питер Наур назвал нотацию Бэкуса нормальной формой Бэкуса (BNF) и упростил ее, чтобы минимизировать используемый набор символов. Однако Дональд Кнут утверждал, что BNF следует рассматривать как форму Бэкуса – Наура , и это стало общепринятым использованием.
Никлаус Вирт определил расширенную форму Бэкуса – Наура (EBNF), усовершенствованную версию BNF, в начале 1970-х годов для PL / 0. Другой вариант — расширенная форма Бэкуса – Наура (ABNF). И EBNF, и ABNF широко используются для определения грамматики языков программирования в качестве входных данных для генераторов синтаксического анализатора и в других областях, таких как определение протоколов связи.
Битовый порядок байтов
Нумерация битов — это концепция, аналогичная порядку байтов, но на уровне битов, а не байтов. Порядок следования битов или порядок следования битов на уровне битов относится к порядку передачи битов по последовательной среде. Битовый аналог little-endian (младший бит идет первым) используется в RS-232 , HDLC , Ethernet и USB . Некоторые протоколы используют обратный порядок (например, телетекст , I 2 C , SMBus , PMBus , SONET и SDH ), а использует один порядок для поля метки, а другой порядок для оставшейся части кадра. Обычно существует согласованное представление битов независимо от их порядка в байте, так что последний становится актуальным только на очень низком уровне. Одно исключение вызвано функцией некоторых циклических проверок избыточности для обнаружения всех пакетных ошибок до известной длины, которая будет испорчена, если порядок битов отличается от порядка байтов при последовательной передаче.
Помимо сериализации, термины « порядок следования битов» и « порядок байтов на уровне битов» используются редко, поскольку редко встречаются компьютерные архитектуры, в которых каждый отдельный бит имеет уникальный адрес. Доступ к отдельным битам или битовым полям осуществляется через их числовые значения или, в языках программирования высокого уровня, через присвоенные имена, последствия которых, однако, могут зависеть от машины или не иметь переносимости программного обеспечения .
На чем написан компилятор?
В 1950-е годы группа разработчиков IBM под руководством Джона Бэкуса разработала первый высокоуровневый язык программирования Fortran, который позволил писать программы на понятном человеку языке. Помимо языка, инженеры работали и над компилятором. Он представлял собой программу с набором исполняемых команд, которая могла компилировать другие программы на Fortran, в том числе и улучшенную версию себя.
В дальнейшем язык Fortran и его компилятор использовали, чтобы написать компиляторы для новых языков программирования. Такой подход используют программисты и в настоящее время.
Писать машинный код долго и неудобно. К тому же, для современных процессоров он может отличаться. Придется писать несколько версий одного и того же компилятора для разных компьютеров. Быстрее и проще написать компилятор на существующем языке программирования. Для этого разработчики выбирают удобный язык и пишут на нем первую версию своего компилятора. Он будет более универсальным для компьютеров и легко скомпилирует улучшенную версию себя.
Зачем нужен компилятор?
Процессор — самая важная часть компьютера. Он обрабатывает информацию, выполняет команды пользователя и следит за работой всех подключенных устройств. Но процессор может разобрать только машинный код — набор 0 и 1, которые записаны в определённом порядке.
Почему именно 0 и 1? В процессор поступают электрические сигналы. Сильный сигнал обозначается цифрой 1, а слабый — 0. Набор таких цифр обозначает какую-то команду. Процессор ее распознает и выполняет.
Программы для первых компьютеров выглядели как огромные наборы 0 и 1. Чтобы записать такую программу, инженеры пользовались гибкими картонными карточками — перфокартами. Цифры на перфокарте записывались поочередно, в несколько строк. Чтобы записать 1, программист делал отверстие в карте. Места без отверстия обозначали 0.
Компьютер считывал перфокарту специальным устройством и выполнял записанную команду. Для одной программы составляли сотни перфокарт.
Писать их было долго и сложно, поэтому инженеры стали создавать языки программирования, обозначая команды словами и знаками. Для того, чтобы процессор понимал, какие команды записаны в программе, программисты создали компилятор — программу, которая преобразует программный код в машинный.
Бинарный (двоичный)!
Теперь, когда мы рассмотрели биты и байты, мы можем сделать небольшой шаг вперёд и перейти к понятию «двоичный». Двоичный как термин может использоваться как указатель двоичного числа (как в нашем однобайтовом примере выше, где мы перешли от 0000 0000 (десятичное 0) до 1111 1111 (десятичное число 255)), или как поток, некоторые данные или состояние.
Например, мы можем говорить о двоичном потоке данных, когда говорим о нулях и единицах, перемещающихся по компьютерной сети. В таком случае (двоичный поток данных) состояние битов не намагничивается или размагничивается, как когда они хранятся на диске или в кэше, а скорее меняется напряжение (например, +5 Вольт), чтобы указать состояние 1 и ноль вольт, чтобы указать состояние 0.
Мы можем использовать слово двоичный для обозначения данных, хранящихся как двоичные (например, на диске), или как состояние, например, исполняемый файл на компьютере часто называется двоичным. Все эти разные виды использования слова «двоичный» требуют немного времени, чтобы привыкнуть к жаргону.
Первые компиляторы
Программное обеспечение для первых компьютеров было в основном написано на ассемблере , а до этого — непосредственно в машинном коде . Обычно для программиста более продуктивно использовать язык высокого уровня, а программы, написанные на языке высокого уровня, можно повторно использовать на разных типах компьютеров . Даже в этом случае компиляторам потребовалось некоторое время, чтобы утвердиться, потому что они генерировали код, который не работал так хорошо, как рукописный ассемблер, они сами по себе были сложными проектами разработки, а очень ограниченный объем памяти ранних компьютеров создал множество технические проблемы для практических реализаций компилятора.
Первый компилятор ALGOL 58 был завершен к концу 1958 года Фридрихом Л. Бауэром , Германом Боттенбрухом, Хайнцем Рутисхаузером и Клаусом Самельсоном для компьютера Z22 . Bauer et al. в предыдущие годы работал над технологией компилятора для Sequentielle Formelübersetzung (т. е. последовательного перевода формул ).
К 1960 году расширенный компилятор Fortran, ALTAC, был доступен на Philco 2000, поэтому вполне вероятно, что программа Fortran была скомпилирована для компьютерных архитектур IBM и Philco в середине 1960 года. Первым известным продемонстрированным кроссплатформенным языком высокого уровня был COBOL . Во время демонстрации в декабре 1960 года программа COBOL была скомпилирована и реализована как на UNIVAC II, так и на RCA 501.
Пример
Пример порядка байтов
С прямым порядком байтов
Little-endian
Эти две диаграммы показывают, как два компьютера, использующие разный порядок байтов, хранят 32-битное (четырехбайтовое) целое число со значением 0x0A0B0C0D . В обоих случаях целое число разбивается на четыре байта, 0x0A , 0x0B , 0x0C и 0x0D , и байты сохраняются в четырех последовательных байтовых ячейках в памяти, начиная с ячейки памяти с адресом a , затем a + 1 , a + 2 и а + 3 . Разница между старшим и младшим порядком байтов заключается в порядке сохранения четырех байтов целого числа.
На левой диаграмме показан компьютер с прямым порядком байтов. При этом начинается сохранение целого числа с наиболее значимым байтом 0x0A по адресу a и заканчивается наименьшим байтом 0x0D по адресу a + 3 .
На правой диаграмме показан компьютер с прямым порядком байтов. Это начинает сохранение целого числа с наименьшим значащим байтом, 0x0D , по адресу a , и заканчивается самым значимым байтом, 0x0A , по адресу a + 3 .
Поскольку каждый компьютер использует одинаковую последовательность байтов для хранения и извлечения целого числа, результаты будут одинаковыми для обоих компьютеров. Проблемы могут возникнуть, когда память адресуется байтами, а не целыми числами, или когда содержимое памяти передается между компьютерами с разным порядком байтов.
дальнейшее чтение
- Кнут, Д.Е. , RUNCIBLE-алгебраический перевод на ограниченном компьютере , Коммуникации ACM, Vol. 2, стр. 18 (ноябрь 1959 г.).
- Айронс, Эдгар Т., Компилятор, управляемый синтаксисом для АЛГОЛА 60 , Сообщения ACM, Vol. 4, стр. 51. (январь 1961 г.).
- Конвей, Мелвин Э. , Дизайн разделимого компилятора диаграмм переходов , Коммуникации ACM, том 6, выпуск 7 (июль 1963 г.)
- Флойд, Р. У. , Синтаксический анализ и приоритет операторов , Журнал ACM, Vol. 10, стр. 316. (июль 1963 г.).
- Cheatham, TE, и Sattley, K., Компиляция , управляемая синтаксисом , SJCC, стр. 31. (1964).
- Рэнделл, Брайан ; Рассел, Лоуфорд Джон, Реализация Алгола 60: перевод и использование программ Алгола 60 на компьютере , Academic Press, 1964
- Кок, Джон ; Шварц, Джейкоб Т. , Языки программирования и их компиляторы: предварительные замечания , технический отчет Института математических наук Куранта , Нью-Йоркский университет , 1969.
- Бауэр, Фридрих Л .; Эйкель, Юрген (ред.), Конструирование компилятора, Продвинутый курс , 2-е изд. Лекционные заметки по информатике 21, Springer 1976, ISBN 3-540-07542-9
- Грис, Дэвид , Конструирование компиляторов для цифровых компьютеров , Нью-Йорк: Wiley, 1971. ISBN 0-471-32776-X
Какие бывают компиляторы?
Ни один компилируемый язык программирования не обходится без компилятора. Некоторые компиляторы работают с несколькими языками программирования. Но программист должен учитывать еще и параметры компьютера, на котором программа будет запускаться.
Дело в том, что современные процессоры отличаются друг от друга устройством, поэтому машинный код для одного процессора будет понятен, а для другого нет. Это касается и операционных систем: одна и та же программа будет работать на Windows, но не запустится на Linux или MacOS. Поэтому нужно пользоваться тем компилятором, который работает с нужным процессором и операционной системой.
Если программа будет работать на нескольких операционных системах, то нужен кросс-компилятор — компилятор, который преобразует универсальный машинный код. Например, GNU Compiler Collection(сокращенно GCC) поддерживает C++, Objective-C, Java, Фортран, Ada, Go и поддерживает разную архитектуру процессоров.
Начинающие программисты даже не знают о наличии компилятора на компьютере. Они пишут программы в интегрированной среде разработки, в которую встроен компилятор, а иногда и не один. В этом случае, выбор компилятора делает среда, а не программист. Например, MS Visual Studio поддерживает компиляторы для операционных систем Windows, Linux, Android. Выбирая тип проекта, Visual Studio определяет процессор и операционную систему компьютера, и после этого выбирает подходящий компилятор.
Оптимизация компиляторов
Оптимизация компилятора — это процесс улучшения качества объектного кода без изменения получаемых им результатов.
Разработчики первого компилятора FORTRAN стремились сгенерировать код, который был бы лучше, чем средний ручной ассемблер, чтобы клиенты действительно могли использовать их продукт. В одном из первых настоящих компиляторов им часто это удавалось.
Более поздние компиляторы, такие как компилятор IBM , уделяли больше внимания хорошей диагностике и более быстрому выполнению за счет оптимизации объектного кода . Только в серии IBM System / 360 IBM предоставила два отдельных компилятора — быстро выполняющийся модуль проверки кода и более медленный, оптимизирующий.
Фрэнсис Э. Аллен , работая самостоятельно и совместно с Джоном Коке , представила многие концепции оптимизации. В статье Аллена 1966 года « Оптимизация программ» было введено использование структур данных графа для кодирования содержимого программы с целью оптимизации. В ее статьях 1970 года « Анализ потока управления» и «Основы для оптимизации программ» определены интервалы как контекст для эффективного и действенного анализа и оптимизации потока данных. В ее статье 1971 года «Кок» «Каталог оптимизирующих преобразований» впервые были даны описание и систематизация оптимизирующих преобразований. Ее статьи 1973 и 1974 годов по анализу межпроцедурных потоков данных распространили анализ на целые программы. В ее статье 1976 года с Коке описана одна из двух основных аналитических стратегий, используемых сегодня при оптимизации компиляторов.
Аллен разработала и реализовала свои методы как часть компиляторов для IBM 7030 Stretch — Harvest и экспериментальной системы Advanced Computing System . Эта работа позволила установить возможности и структуру современных машинно-независимых оптимизаторов. Затем она основала и возглавила проект PTRAN по автоматическому параллельному выполнению программ FORTRAN. Ее команда PTRAN разработала новые схемы обнаружения параллелизма и создала концепцию графа зависимости программы, основного метода структурирования, используемого большинством распараллеливающих компиляторов.
«Языки программирования и их компиляторы » Джона Кока и Джейкоба Шварца , опубликованные в начале 1970 года, посвятили более 200 страниц алгоритмам оптимизации. Он включал многие из уже знакомых нам техник, таких как устранение избыточного кода и снижение стойкости .
Оптимизация глазка
Оптимизация с помощью глазка — это простой, но эффективный метод оптимизации. Он был изобретен Уильямом М. МакКиманом и опубликован в 1965 году в CACM. Он использовался в компиляторе XPL, который помог разработать МакКиман.
Оптимизатор Capex COBOL
Корпорация Capex разработала «Оптимизатор COBOL» в середине 1970-х годов для COBOL . В данном случае оптимизатор этого типа зависел от знания «слабых мест» стандартного компилятора IBM COBOL и фактически заменял (или исправлял ) разделы объектного кода более эффективным кодом. Код замены может заменить линейный поиск в таблице с помощью бинарного поиска , например , или иногда просто заменить относительно «медленный» команду с известной быстрее той , которая была в противном случае функционально эквивалентны в его контексте. Эта техника теперь известна как « Снижение силы ». Например, на IBM System / 360 аппаратных средства CLI инструкция была, в зависимости от конкретной модели, между дважды и 5 раз быстрее, чем в CLC инструкции для отдельных сравнений байт.
Современные компиляторы обычно предоставляют параметры оптимизации, позволяющие программистам выбирать, выполнять или нет проход оптимизации.
Метакомпиляторы
Метакомпиляторы отличаются от генераторов парсеров тем, что принимают на входе программу, написанную на . Их входные данные состоят из формулы анализа грамматики в сочетании со встроенными операциями преобразования, которые создают абстрактные синтаксические деревья или просто выводят переформатированные текстовые строки, которые могут быть стеками машинного кода.
Многие из них могут быть запрограммированы на их собственном метаязыке, что позволяет им компилировать себя, что делает их самообслуживаемыми компиляторами расширяемых языков.
Многие метакомпиляторы на работе . Его компилятор META II , впервые выпущенный в 1964 году, был первым документированным метакомпилятором. Имея возможность определять свой собственный язык и другие языки, META II приняла со встроенным выводом (производство кода) . Он также был переведен на один из самых ранних экземпляров виртуальной машины . Лексический анализ проводился с помощью встроенных функций распознавания токенов: .ID, .STRING и .NUMBER. Строки в кавычках в синтаксической формуле распознают несохраняемые лексемы.
TREE-META , метакомпилятор Шорре второго поколения, появился примерно в 1968 году. Он расширил возможности META II, добавив неанализируемые правила, отделяющие создание кода от анализа грамматики. Операции преобразования дерева в синтаксической формуле создают абстрактные синтаксические деревья , над которыми работают неанализируемые правила. Неразборчивое сопоставление с образцом дерева обеспечивало возможность оптимизации глазком .
, описанный в публикации ACM 1970 года, представляет собой метакомпилятор Шорре третьего поколения, который добавляет к грамматическому анализу правила лексирования и операторы поиска с возвратом. LISP 2 был связан с нечеткими правилами TREEMETA на языке генератора CWIC. Благодаря обработке LISP 2 ] CWIC может генерировать полностью оптимизированный код. CWIC также обеспечил генерацию двоичного кода в разделах именованного кода. Одно- и многопроходные компиляции могут быть реализованы с использованием CWIC.
CWIC скомпилирован в 8-битные инструкции машинного кода с байтовой адресацией, в первую очередь предназначенные для создания кода IBM System / 360.
Более поздние поколения не документированы публично. Одной из важных функций могла бы быть абстракция набора команд целевого процессора, генерирующая в псевдомашинный набор команд макросы, которые можно было бы отдельно определить или сопоставить с инструкциями реальной машины. Оптимизация, применяемая к последовательным инструкциям, затем может быть применена к псевдоинструкции перед их расширением до целевого машинного кода.
Проблемы совместимости и конвертация
Запись многобайтового числа из памяти компьютера в файл или передача по сети требует соблюдения соглашений о том, какой из байтов передается первым. Прямая запись в том порядке, в котором байты расположены в ячейках памяти приводит к проблемам при переносе приложения с платформы на платформу.
Для преобразования между сетевым порядком байтов (англ. network byte order), который всегда big-endian, и порядком байтов, использующимся на машине (англ. host byte order), стандарт POSIX предусматривает функции , , , :
- — конвертирует 32-битную беззнаковую величину из локального порядка байтов в сетевой;
- — конвертирует 16-битную беззнаковую величину из локального порядка байтов в сетевой;
- — конвертирует 32-битную беззнаковую величину из сетевого порядка байтов в локальный;
- — конвертирует 16-битную беззнаковую величину из сетевого порядка байтов в локальный.
В случае совпадения текущего порядка байтов и сетевого, функции могут быть «пустыми» (то есть, не менять порядка байтов). Стандарт также допускает, чтобы эти функции были реализованы макросами.
Существует много языков и библиотек со средствами конвертации в оба основных порядка байт и обратно.
Ядро Linux: le16_to_cpu(), cpu_to_be32(), cpu_to_le16p(), и так далее;
Ядро FreeBSD: htobe16(), le32toh(), и так далее;
Erlang:
<<Count32big-unsigned-integer, Average64big-float>> = Chunk Message = <<Length32little-unsigned-integer, MType16little-unsigned-integer, MessageBody>>
Python:
import struct Count, Average = struct.unpack(">Ld", Chunk) Message = struct.pack("<LH", Length, MType) + MessageBody
Perl:
($Count, $Average) = unpack('L>d>', $Chunk); $Message = pack('(LS)<', $Length, $MType) . $MessageBody; (или то же самое $Message = pack('Vv', $Length, $MType) . $MessageBody;)
данные примеры для Erlang, Python, Perl содержат идентичную функциональность.
Процессоры Intel x86-64 имеют инструкцию BSWAP для смены порядка байт.
Юникод
Если Юникод записан в виде UTF-16 или UTF-32, то порядок байтов является существенным. Одним из способов обозначения порядка байтов в юникодовых текстах является постановка в начале специального символа BOM (byte order mark, маркер последовательности байтов, U+FEFF) — «перевёрнутый» вариант этого символа (U+FFFE) не существует и не допускается в текстах.
Символ U+FEFF изображается в UTF-16 последовательностью байтов 0xFE 0xFF (big-endian) или 0xFF 0xFE (little-endian), а в UTF-32 — последовательностью 0x00 0x00 0xFE 0xFF (big-endian) или 0xFF 0xFE 0x00 0x00 (little-endian).
Исторический
Раннее компьютерное программное обеспечение было написано на языке ассемблера . Языки программирования высокого уровень (в абстракции слоях ) не был изобретен , пока выгоды от возможности повторного использования программного обеспечения на различных типах процессоров не стали более важными , чем стоимость записи на «компилятор. Очень ограничена память емкость ранних компьютеров также создает ряд технических проблем , в разработке компиляторов.
В конце 1950- х годов впервые появились машинно-независимые языки программирования. Впоследствии разрабатываются несколько экспериментальных компиляторов. Первый компилятор, A-0 System (для языка A-0) был написан Грейс Хоппер в 1952 году. Считается, что команда FORTRAN под руководством Джона Бэкуса из IBM разработала первый полный компилятор в 1957 году. COBOL , разработанный в 1959 году. и в значительной степени основанный на идеях Грейс Хоппер, это первый язык, скомпилированный на нескольких архитектурах.
В нескольких областях применения Идея использования языка более высокого уровня абстракции быстро распространилась. С увеличением функциональности, поддерживаемой новыми языками программирования, и возрастающей сложностью компьютерной архитектуры компиляторы становятся все более и более сложными.
В 1962 году Тим Харт и Майк Левин из Массачусетского технологического института (Массачусетского технологического института) создали для Лиспа первый » автономный » компилятор, способный компилировать в объектный код, собственный исходный код которого выражен на языке высокого уровня . Начиная с 1970-х годов стало обычным делом разрабатывать компилятор на языке, который он собирался компилировать, что сделало Паскаль и Си очень популярными языками разработки.
Мы также можем использовать язык или среду, специализирующуюся на разработке компиляторов: мы говорим во время инструментов мета-компиляции, и мы используем, например, компилятор компилятора . Этот метод особенно полезен для создания первого компилятора нового языка; использование адаптированного и строгого языка затем способствует развитию и эволюции.
Раздельная компиляция
Раздельная компиляция (англ. separate compilation) — трансляция частей программы по отдельности с последующим объединением их компоновщиком в единый загрузочный модуль.
Исторически особенностью компилятора, отражённой в его названии (англ. compile — собирать вместе, составлять), являлось то, что он производил как трансляцию, так и компоновку, при этом компилятор мог порождать сразу машинный код. Однако позже, с ростом сложности и размера программ (и увеличением времени, затрачиваемого на перекомпиляцию), возникла необходимость разделять программы на части и выделять библиотеки, которые можно компилировать независимо друг от друга. При трансляции каждой части программы компилятор порождает объектный модуль, содержащий дополнительную информацию, которая потом, при компоновке частей в исполнимый модуль, используется для связывания и разрешения ссылок между частями.
Появление раздельной компиляции и выделение компоновки как отдельной стадии произошло значительно позже создания компиляторов. В связи с этим вместо термина «компилятор» иногда используют термин «транслятор» как его синоним: либо в старой литературе, либо когда хотят подчеркнуть его способность переводить программу в машинный код (и наоборот, используют термин «компилятор» для подчёркивания способности собирать из многих файлов один).
Как работает компилятор?
Преобразование программного кода в машинный называется компиляцией. Компиляция только преобразует код. Она не запускает его на исполнение. В этот момент он “статически” (то есть без запуска) транслируется в машинный код. Это сложный процесс, в котором сначала текст программы разбирается на части и анализируется, а затем генерируется код, понятный процессору.
Разберём этапы компиляции на примере вычисления периметра прямоугольника:
После запуска программы компилятору нужно определить, какие команды в ней записаны. Сначала компилятор разделяет программу на слова и знаки — токены, и записывает их в список. Такой процесс называется лексическим анализом. Его главная задача — получить токены.
Затем компилятор читает список и ищет токен-операторы. Это могут быть оператор присваивания(), арифметические операторы(,,,), оператор вывода() и другие операторы языка программирования. Такие операторы работают с числами, текстом и переменными.
Компилятор должен понять, какие токены в списке связаны с токен-оператором. Чтобы сделать это правильно, для каждого оператора строится специальная структура — логическое дерево или дерево разбора.
Так операция будет преобразована в логическое дерево:
Теперь каждое дерево нужно разобрать на команды, и каждую команду преобразовать в машинный код.
Компилятор начинает читать дерево снизу вверх и составляет список команд:
- Взять переменную , взять переменную , сложить их
- Взять результат сложения, взять число и найти их произведение
- Результат произведения присвоить (записать) в переменную
Компилятор еще раз проверяет команды, находит ошибки и старается улучшить код. При успешном завершении этого этапа, компилятор переводит каждую команду в набор 0 и 1. Наборы записываются в файл, который сможет прочитать и выполнить процессор.
Этимология названия
Термины big-endian и little-endian первоначально не имели отношения к информатике. В сатирическом произведении Джонатана Свифта «Путешествия Гулливера» описываются вымышленные государства Лилипутия и Блефуску, в течение многих лет ведущие между собой войны из-за разногласия по поводу того, с какого конца следует разбивать варёные яйца. Тех, кто считает, что их нужно разбивать с тупого конца, в произведении называют Big-endians («тупоконечники»). Споры между сторонниками big-endian и little-endian в информатике также часто носят характер т. н. «религиозных войн».
Термины big-endian и little-endian ввёл Коэн в 1980 году в своей статье «On Holy Wars and a Plea for Peace».
Выводы и рекомендации
Компилятор — переводчик между программистом и процессором
Он преобразует текст программы в машинный код, определяет ряд ошибок в программе и оптимизирует ее работу.
Выбирая, где компилировать программу, важно помнить о том, что машинный код для процессоров и операционных систем будет разным, и подобрать правильный компилятор.
Чем точнее компилятор определит команды, тем корректнее и быстрее будет работать программа. Для этого следуйте простым рекомендациям:
- использовать простые, понятные команды;
- помнить о соответствии типов данных;
- внимательно набирать код, избегая синтаксических ошибок;
- избегать повторяющихся действий и бесполезных переменных.