Компиляция программы на C ++ включает три этапа:
Предварительная обработка: препроцессор берет файл исходного кода C ++ и обрабатывает #include
s, #define
s и другие директивы препроцессора. Результатом этого шага является «чистый» файл C ++ без директив препроцессора.
Компиляция: компилятор принимает выходные данные препроцессора и создает из него объектный файл.
Связывание: компоновщик берет объектные файлы, созданные компилятором, и создает либо библиотеку, либо исполняемый файл.
Препроцессирование
Препроцессор обрабатывает директивы препроцессора , такие как #include
и #define
. Он не зависит от синтаксиса C ++, поэтому его следует использовать с осторожностью.
Работает с одним исходным файлом C ++ одновременно, заменяя директивы #include
содержимым соответствующих файлов (обычно это просто объявления), заменяя макросы (#define
) и выбирая различные части текста. в зависимости от директив #if
, #ifdef
и #ifndef
.
Препроцессор работает с потоком токенов предварительной обработки. Подстановка макросов определяется как замена токенов другими токенами (оператор ##
позволяет объединить два токена, когда это имеет смысл).
После всего этого препроцессор выдает один вывод, который представляет собой поток токенов, полученных в результате преобразований, описанных выше. Он также добавляет некоторые специальные маркеры, которые сообщают компилятору о происхождении каждой строки, чтобы он мог использовать их для создания разумных сообщений об ошибках.
На этом этапе могут возникать некоторые ошибки при грамотном использовании директив #if
и #error
.
Компиляция
Этап компиляции выполняется на каждом выходе препроцессора. Компилятор анализирует чистый исходный код C ++ (теперь без каких-либо директив препроцессора) и преобразует его в ассемблерный код. Затем вызывает базовый сервер (ассемблер в инструментальной цепочке), который собирает этот код в машинный код, создавая настоящий двоичный файл в некотором формате (ELF, COFF, a.out, ...). Этот объектный файл содержит скомпилированный код (в двоичном виде) символов, определенных во входных данных. Символы в объектных файлах называются по имени.
Объектные файлы могут ссылаться на символы, которые не определены. Это тот случай, когда вы используете декларацию, а не даете ей определение. Компилятор не возражает против этого и с удовольствием создаст объектный файл, если исходный код правильно сформирован.
Компиляторы обычно позволяют вам остановить компиляцию на этом этапе. Это очень полезно, потому что с его помощью вы можете скомпилировать каждый файл исходного кода отдельно. Преимущество этого в том, что вам не нужно перекомпилировать все , если вы изменяете только один файл.
Созданные объектные файлы могут быть помещены в специальные архивы, называемые статическими библиотеками, для более легкого последующего использования.
На этом этапе сообщается о «обычных» ошибках компилятора, таких как синтаксические ошибки или ошибки разрешения перегрузки.
Linking
Линкер - это то, что создает окончательный вывод компиляции из объектных файлов, созданных компилятором. Эти выходные данные могут быть либо общей (или динамической) библиотекой (и, хотя имя схоже, они не имеют много общего со статическими библиотеками, упомянутыми ранее), либо исполняемым файлом.
Связывает все объектные файлы, заменяя ссылки на неопределенные символы правильными адресами. Каждый из этих символов может быть определен в других объектных файлах или в библиотеках. Если они определены в библиотеках, отличных от стандартной библиотеки, вам необходимо сообщить о них компоновщику.
На этом этапе наиболее распространенными ошибками являются пропущенные определения или дубликаты определений. Первое означает, что либо определения не существуют (т.е. они не записаны), либо объектные файлы или библиотеки, в которых они находятся, не были переданы компоновщику. Последнее очевидно: один и тот же символ был определен в двух разных объектных файлах или библиотеках.