После небольшого исследования компиляторов и того, как они работают, я узнал, что процесс часто разбивается на 4 этапа: препроцессор, компилятор, ассемблер и компоновщик. То, как я представлял эти шаги, представляло собой отдельную программу; Программа препроцессора, программа компилятора, программа ассемблера и программа компоновщика. Однако вы узнаете, что иногда процесс создания ассемблерного кода и создания объектных файлов все обрабатывается программой компилятора, а иногда — нет. Кажется, это очень сильно зависит от контекста и используемого языка программирования. Мой вопрос тогда, как типичный процесс перевода разбивается для перевода исходного кода C ++ в машинный код?
Примечание: мой вопрос отличается от других потоков компилятора C ++, поскольку я спрашиваю не только о том, как работает компилятор, но и о том, существуют ли некоторые другие процессы, такие как компоновка, собственные исполняемые программы или они обычно встроены в программу компилятора.
Все современные компиляторы (по крайней мере, gcc и clang, но я сомневаюсь, что другие сильно отличаются) имеют предварительную обработку и компилятор как один исполняемый файл. Это происходит главным образом потому, что компилятор хочет иметь возможность генерировать хорошие сообщения об ошибках [которые указывают на правую строку и столбец, а когда он задействован в макросах, может сказать «Вызывается из макроса FOO (x)») и понимать «какой файл мы «войти» легче, когда компилятор имеет фактический исходный код, а не предварительно обработанный код.
Компоновщик, как правило, представляет собой отдельную программу, и ассемблер используется только для встроенного ассемблерного кода [как правило, как встроенная часть компилятора] — в противном случае компилятор будет генерировать машинный код напрямую, без использования ассемблера [по крайней мере в LLVM, который является компилятор, который я знаю лучше всего. Таким образом, из компилятора выходит полностью сформированный объектный файл.
Если у вас есть правильные параметры, будет вызван компоновщик, но это отдельный исполняемый файл, который свяжет объектный файл вместе с библиотекой времени выполнения и стартовым кодом «перед основным» (создание глобального объекта и т. П., А также « готовлюсь к вызову главного «). Это создаст исполняемый файл.
С другими опциями компилятор создаст только объектный файл или дизассемблирование машинного кода, сгенерированного в символической форме ( -S
опция).
Внутренняя часть компилятора, отвечающая за генерацию кода, также обычно занимается оптимизацией и различными преобразованиями кода, чтобы помочь этапам оптимизации — например, Clang + LLVM будет генерировать «однородные» циклы, независимо от того, использовали ли вы while
, for
или же goto
сделать петлю.
Это помогает более продвинутым этапам не определять много разных форм циклов и позволяет компилятору генерировать «хороший» код независимо от того, как программист сформировал цикл. [Конечно, если вы сделаете это достаточно сложным, компилятор, вероятно, не совсем выяснит, как работает ваш цикл, и не будет так хорошо оптимизировать, но для прямого преобразования между основными формами он будет выполнять одно и то же конечное генерирование кода независимо от того, о том, как источник выглядел].
Других решений пока нет …