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