Каковы этапы компиляции программы на C++?

Стадии компиляции программы на C++ определены стандартом?

Если так, то кто они?

Если нет, то ответ для широко используемого компилятора (я бы предпочел MSVS) был бы отличным.

Я говорю о предварительной обработке, токенизации, разборе и тому подобном. В каком порядке они выполняются и что они делают в частности?

РЕДАКТИРОВАТЬ: Я знаю, что делают компиляция, связывание и предварительная обработка, меня больше всего интересуют другие и порядок. Разъяснения к ним, конечно, также приветствуются, так как, возможно, я не единственный, кто заинтересован в ответе.

3 ответа

Решение

Стадии компиляции программы на C++ определены стандартом?

И да и нет.

Стандарт C++ определяет 9 "фаз перевода". Цитата из проекта N3242 (10 МБ в формате PDF) от 2011-02-28 (до выпуска официального стандарта C++11), раздел 2.2:

Приоритет среди синтаксических правил перевода определяется следующими этапами [см. Сноску].

  1. Физические символы исходного файла отображаются, в зависимости от реализации, в базовый исходный набор символов (ввод символов новой строки для индикаторов конца строки), если это необходимо. [СНиП]
  2. Каждый экземпляр символа обратной косой черты (\), за которым сразу следует символ новой строки, удаляется, объединяя физические исходные строки для формирования логических исходных строк. [СНиП]
  3. Исходный файл разбит на токены предварительной обработки (2.5) и последовательности символов пробела (включая комментарии). [СНиП]
  4. Выполняются директивы предварительной обработки, расширяются вызовы макросов и выполняются выражения унарного оператора _Pragma. [СНиП]
  5. Каждый элемент исходного набора символов в символьном литерале или строковом литерале, а также каждая escape-последовательность и универсальное имя-символа в символьном литерале или неочищенном строковом литерале преобразуется в соответствующий элемент набора исполняемых символов; [СНиП]
  6. Литеральные токены соседних строк объединяются.
  7. Пробелы, разделяющие токены, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. (2.7). Полученные токены синтаксически и семантически анализируются и переводятся как единица перевода. [СНиП]
  8. Переведенные единицы перевода и единицы реализации объединяются следующим образом: [SNIP]
  9. Все ссылки на внешние объекты разрешены. Компоненты библиотеки связаны для удовлетворения внешних ссылок на объекты, не определенные в текущем переводе. Весь такой вывод транслятора собирается в образ программы, который содержит информацию, необходимую для выполнения в среде выполнения.

[сноска] Реализации должны вести себя так, как будто эти отдельные фазы происходят, хотя на практике разные фазы могут складываться вместе.

Как указывают маркеры [SNIP], я не процитировал весь раздел, просто достаточно, чтобы изложить идею.

Чтобы подчеркнуть, компиляторы не обязаны следовать этой точной модели, если конечный результат такой, как если бы они это сделали.

Фазы 1-6 в большей или меньшей степени соответствуют препроцессору, 7 - тому, что вы обычно можете считать компиляцией, 8 - шаблонам, а 9 - линковке.

(Фазы перевода С аналогичны, но № 8 опущен.)

9 так называемых "фаз перевода" перечислены в стандарте в [lex.phases] (2.2 в C++11, 2.1 в C++03).

Детали, требуемые в стандарте, различаются: предварительная обработка разбита на несколько этапов, потому что в различных точках стандарта важно, что именно "уже сделано" и что "осталось сделать", когда определен определенный фрагмент поведения. Так что, хотя он не говорит вам, как написать лексер, он дает вам довольно четкую дорожную карту.

С другой стороны, связывание оставлено в основном для реализации, чтобы решить, как на самом деле это достигается, потому что стандарту не важно, как выглядит данное имя, а только то, к чему оно относится.

Он также не дает никаких подробностей о синтаксическом анализе, он просто говорит: "Полученные токены синтаксически и семантически анализируются и переводятся". Это потому, что целые главы 3-15 должны заполнить эту деталь.

В нем вообще не упоминаются внутренние представления во время синтаксического анализа / перевода, и при этом не упоминаются этапы оптимизации - они важны для проектирования компиляторов, но они не важны для стандарта. Оптимизация может происходить в разных местах в разных компиляторах. Долгое время оптимизация была почти полностью на этапе компиляции, прежде чем создавать объектные файлы, и компоновщики были глупы как пост. Я думаю, что теперь серьезные реализации C++ могут выполнить хотя бы некоторую оптимизацию для нескольких TU. Так что "другие" не просто остаются вне стандарта, они действительно меняются со временем.

Спецификация C++ намеренно размыта во многих отношениях, в основном, чтобы оставаться независимой от реализации. Многие области, где язык нечеткий, больше не представляют большой проблемы - например, вы обычно можете полагаться на 8-битный символ. Тем не менее, другие вопросы, такие как расположение структур, которые используют множественное наследование, представляют собой реальную проблему, как и влияние виртуальных функций на классы. Эти проблемы влияют на совместимость кода, созданного различными компиляторами. Двоичный интерфейс приложения (или ABI) C++ не является строго определенным, и в результате вам иногда приходится заглядывать в C, где это становится проблематичным. Написание интерфейса плагина является хорошим примером.

Точно так же стандарт не дает подробного описания того, как должен быть построен компилятор, потому что есть много ключевых решений и функций, которые отличают компиляторы. Например, MSVC может выполнять частичные сборки (позволяя редактировать и продолжать), а GCC - нет. Тем не менее, в общем, все компиляторы выполняют одинаковые этапы: предварительная обработка, синтаксический анализ, определение потока программы, создание таблицы символов и создание линейной серии инструкций, которые впоследствии могут быть связаны для создания исполняемого файла. Да, и связывая эти объектные файлы, обычно это делает компоновщик.

Я кратко посмотрел, довольно сложно найти описания отдельных компиляторов. Я сомневаюсь, что есть много коммерческих компиляторов, таких как предложения Microsoft, исключительно по коммерческим причинам. GCC - ваш лучший выбор, хотя Microsoft с удовольствием опишет этот процесс. Это довольно банальная вещь: все компиляторы работают примерно одинаково. Настоящее золото в том, как они выполняют эти этапы, алгоритмы и структуры данных, которые они используют. В этом отношении я рекомендую эту книгу. Я купил совершенно новый экземпляр для университетского курса несколько лет назад, и я позаимствовал большинство своих учебников из библиотеки:).

Другие вопросы по тегам