Компиляция программы на C ++ состоит из трех этапов:
Предварительная обработка: препроцессор берет файл исходного кода C ++ и обрабатывает #include
s, #define
s и другие директивы препроцессора. Результатом этого шага является «чистый» файл C ++ без директив препроцессора.
Компиляция: компилятор берет вывод препроцессора и создает из него объектный файл.
Связывание: компоновщик берет объектные файлы, созданные компилятором, и создает либо библиотеку, либо исполняемый файл.
предварительная обработка
Препроцессор обрабатывает директивы препроцессора , такие как #include
и #define
. Он не зависит от синтаксиса C ++, поэтому его следует использовать с осторожностью.
Он работает на одном C ++ исходного файла , в то время, заменив #include
директивы с содержанием соответствующих файлов (которые, как правило , только декларация), делая замену макросов ( #define
), и выбирая различные части текста , в зависимости от #if
, #ifdef
и #ifndef
директивы.
Препроцессор работает с потоком токенов предварительной обработки. Подстановка макросов определяется как замена токенов другими токенами (оператор ##
позволяет объединить два токена, когда это имеет смысл).
После всего этого препроцессор выдает один выход, который представляет собой поток токенов, полученных в результате преобразований, описанных выше. Он также добавляет некоторые специальные маркеры, которые сообщают компилятору о происхождении каждой строки, чтобы он мог использовать их для создания разумных сообщений об ошибках.
Некоторые ошибки могут быть получены на этом этапе при умном использовании #if
и#error
директив.
компиляция
Этап компиляции выполняется на каждом выходе препроцессора. Компилятор анализирует чистый исходный код C ++ (теперь без каких-либо директив препроцессора) и преобразует его в ассемблерный код. Затем вызывает базовый сервер (ассемблер в инструментальной цепочке), который собирает этот код в машинный код, создавая настоящий двоичный файл в некотором формате (ELF, COFF, a.out, ...). Этот объектный файл содержит скомпилированный код (в двоичном виде) символов, определенных во входных данных. Символы в объектных файлах называются по имени.
Объектные файлы могут ссылаться на символы, которые не определены. Это тот случай, когда вы используете декларацию, а не даете ей определение. Компилятор не возражает против этого и с удовольствием создаст объектный файл, если исходный код правильно сформирован.
Компиляторы обычно позволяют вам остановить компиляцию на этом этапе. Это очень полезно, потому что с его помощью вы можете скомпилировать каждый файл исходного кода отдельно. Преимущество этого в том, что вам не нужно перекомпилировать все если вы изменяете только один файл.
Созданные объектные файлы могут быть помещены в специальные архивы, называемые статическими библиотеками, для более легкого повторного использования в дальнейшем.
Именно на этом этапе сообщается о «обычных» ошибках компилятора, таких как синтаксические ошибки или ошибки разрешения перегрузки.
соединение
Линкер - это то, что создает окончательный вывод компиляции из объектных файлов, созданных компилятором. Эти выходные данные могут быть либо общей (или динамической) библиотекой (и, хотя имя схоже, они не имеют много общего со статическими библиотеками, упомянутыми ранее), либо исполняемым файлом.
Он связывает все объектные файлы, заменяя ссылки на неопределенные символы правильными адресами. Каждый из этих символов может быть определен в других объектных файлах или в библиотеках. Если они определены в библиотеках, отличных от стандартной библиотеки, вам необходимо сообщить о них компоновщику.
На этом этапе наиболее распространенными ошибками являются пропущенные определения или дубликаты определений. Первое означает, что либо определения не существуют (т.е. они не записаны), либо объектные файлы или библиотеки, в которых они находятся, не были переданы компоновщику. Последнее очевидно: один и тот же символ был определен в двух разных объектных файлах или библиотеках.
Эта тема обсуждается на CProgramming.com:
https://www.cprogramming.com/compilingandlinking.html.
Вот что написал автор:
источник
На стандартном фронте:
единица перевода является комбинацией исходных файлов, включенных заголовков и исходных файлов менее каких - либо исходных линий пропускаемых условной директивы включения препроцессора.
Стандарт определяет 9 этапов перевода. Первые четыре соответствуют предварительной обработке, следующие три - это компиляция, следующая - создание шаблонов ( создание блоков создания экземпляров ), а последняя - это связывание.
На практике восьмой этап (создание шаблонов) часто выполняется во время процесса компиляции, но некоторые компиляторы задерживают его на этапе компоновки, а некоторые распространяют его на два.
источник
Сложным является то, что ЦП загружает данные с адресов памяти, сохраняет данные по адресам памяти и выполняет инструкции последовательно из адресов памяти с некоторыми условными переходами в последовательности обработанных инструкций. Каждая из этих трех категорий команд включает в себя вычисление адреса ячейки памяти, которая будет использоваться в машинной инструкции. Поскольку машинные инструкции имеют переменную длину в зависимости от конкретной задействованной инструкции, и поскольку мы объединяем их в переменную длину вместе, когда мы строим наш машинный код, существует два шага, связанных с вычислением и построением любых адресов.
Сначала мы раскладываем распределение памяти как можно лучше, прежде чем узнаем, что именно происходит в каждой ячейке. Мы выясняем байты, или слова, или что-либо, что формирует инструкции, литералы и любые данные. Мы просто начинаем выделять память и строим значения, которые создадут программу по ходу работы, и записываем все, что нам нужно, чтобы вернуться и исправить адрес. В этом месте мы помещаем пустышку, чтобы просто заполнить местоположение, чтобы мы могли продолжить вычислять объем памяти. Например, наш первый машинный код может занимать одну ячейку. Следующий машинный код может занять 3 ячейки, включая одну ячейку машинного кода и две ячейки адреса. Теперь наш адресный указатель равен 4. Мы знаем, что происходит в ячейке машины, которая является кодом операции, но нам нужно подождать, чтобы вычислить, что идет в ячейках адреса, пока мы не узнаем, где эти данные будут расположены, т.е.
Если бы был только один исходный файл, компилятор теоретически мог бы создать полностью исполняемый машинный код без компоновщика. В двухпроходном процессе он может вычислить все фактические адреса для всех ячеек данных, на которые ссылается любая инструкция загрузки или сохранения компьютера. И он может вычислить все абсолютные адреса, на которые ссылаются любые инструкции абсолютного перехода. Вот как работают более простые компиляторы, такие как в Forth, без компоновщика.
Линкер - это то, что позволяет компилировать блоки кода отдельно. Это может ускорить весь процесс создания кода и дает некоторую гибкость в отношении того, как блоки используются позже, другими словами, их можно перемещать в памяти, например, добавляя 1000 к каждому адресу, чтобы поднять блок на 1000 ячеек адреса.
Таким образом, то, что выводит компилятор, - это грубый машинный код, который еще не полностью собран, но выложен так, что мы знаем размер всего, другими словами, чтобы мы могли начать вычислять, где будут располагаться все абсолютные адреса. Компилятор также выводит список символов, которые являются парами имя / адрес. Символы связывают смещение памяти в машинном коде в модуле с именем. Смещение является абсолютным расстоянием до места нахождения символа в модуле.
Вот где мы доберемся до компоновщика. Компоновщик сначала соединяет все эти блоки машинного кода вместе и записывает, где начинается каждый из них. Затем он вычисляет адреса, которые должны быть зафиксированы, складывая относительное смещение в модуле и абсолютную позицию модуля в большем макете.
Очевидно, я упростил это, так что вы можете попытаться понять это, и я сознательно не использовал жаргон объектных файлов, таблиц символов и т. Д., Что для меня является частью путаницы.
источник
GCC компилирует программу на C / C ++ в исполняемый файл в 4 этапа.
Например,
gcc -o hello hello.c
осуществляется следующим образом:1. Предварительная обработка
Предварительная обработка через GNU C Preprocessor (
cpp.exe
), который включает заголовки (#include
) и расширяет макросы (#define
).Результирующий промежуточный файл "hello.i" содержит расширенный исходный код.
2. Компиляция
Компилятор компилирует предварительно обработанный исходный код в код сборки для конкретного процессора.
Опция -S указывает на создание кода сборки вместо объектного кода. Результирующий файл сборки - "hello.s".
3. Сборка
Ассемблер (
as.exe
) преобразует ассемблерный код в машинный код в объектном файле "hello.o".4. Линкер
Наконец, linker (
ld.exe
) связывает объектный код с библиотечным кодом, чтобы создать исполняемый файл "привет".источник
Посмотрите на URL: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
Полный процесс компиляции C ++ четко представлен в этом URL.
источник