Этапы трансляции
Исходные файлы C++ обрабатываются компилятором для создания программ на C++.
Процесс трансляции
Текст программы на C++ хранится в единицах, называемых исходными файлами.
Исходные файлы C++ проходят трансляцию, в результате которой получается единица трансляции, состоящая из следующих этапов:
- Каждый исходный файл преобразуется в последовательность символов.
- Каждая последовательность символов преобразуется в последовательность токенов препроцессора, разделённых пробельными символами.
- Каждый токен препроцессора преобразуется в токен, образуя последовательность токенов.
- Каждая последовательность токенов преобразуется в единицу трансляции.
Программа на C++ может быть сформирована из транслированных единиц трансляции. Единицы трансляции могут транслироваться отдельно, а затем связываться для создания исполняемой программы.
Описанный выше процесс можно разделить на 9 этапов трансляции.
Токены препроцессора
Токен препроцессора — минимальный лексический элемент языка на этапах трансляции с 3-го по 6-й.
К категориям токенов препроцессора относятся:
-
имена заголовков (например,
<iostream>или"myfile.h")
| (начиная с C++20) |
- идентификаторы
- числа препроцессора (см. ниже)
- символьные литералы, включая пользовательские символьные литералы(начиная с C++11)
- строковые литералы, включая пользовательские строковые литералы(начиная с C++11)
- операторы и знаки пунктуации, включая альтернативные токены
- отдельные символы, не являющиеся пробельными и не относящиеся ни к одной другой категории
- апостроф (
', U+0027), - кавычка (
", U+0022) или - символ, не входящий в базовый набор символов.
Числа препроцессора
Множество токенов препроцессора категории «число препроцессора» является надмножеством объединения множеств токенов целочисленных литералов и литералов с плавающей точкой:
.(необязательно) digit pp-continue-seq (необязательно) |
| digit | - | одна из цифр от 0 до 9 |
| pp-continue-seq | - | последовательность из элементов pp-continue |
Каждый pp-continue — это один из следующих элементов:
| identifier-continue | (1) | |
| exp-char sign-char | (2) | |
. | (3) | |
’ digit | (4) | (начиная с C++14) |
’ nondigit | (5) | (начиная с C++14) |
| identifier-continue | - | любой символ, кроме первого, допустимого идентификатора |
| exp-char | - | один из P, p,(начиная с C++11) E и e |
| sign-char | - | один из + и - |
| digit | - | одна из цифр от 0 до 9 |
| nondigit | - | одна из латинских букв A/a–Z/z или символ подчёркивания |
Число препроцессора не имеет типа или значения; оно получает их после успешного преобразования в токен целочисленного литерала или литерала с плавающей точкой.
Пробельные символы
Пробельные символы включают комментарии, символы пробела или и то и другое.
К пробельным символам относятся:
- символ горизонтальной табуляции (U+0009)
- перевод строки (U+000A)
- символ вертикальной табуляции (U+000B)
- символ перевода страницы (U+000C)
- пробел (U+0020)
Пробельные символы обычно используются для разделения токенов препроцессора, за исключением следующих случаев:
- Они не служат разделителями в имени заголовка, символьном литерале и строковом литерале.
- Токены препроцессора, разделённые пробельными символами, содержащими символы перевода строки, не могут образовывать директивы препроцессора.
#include "my header" // OK, using a header name containing whitespace #include/*hello*/<iostream> // OK, using a comment as whitespace #include <iostream> // Error: #include cannot span across multiple lines "str ing" // OK, a single preprocessing token (string literal) ' ' // OK, a single preprocessing token (character literal)
Правило максимального поглощения
Если входные данные уже разобраны на токены препроцессора до некоторого символа, следующий токен препроцессора обычно считается самой длинной последовательностью символов, которая может образовывать токен препроцессора, даже если это приведёт к ошибке последующего анализа. Это обычно называется правилом максимального поглощения.
int foo = 1; int bar = 0xE+foo; // Error: invalid preprocessing number 0xE+foo int baz = 0xE + foo; // OK
Иными словами, правило максимального поглощения отдаёт предпочтение многосимвольным операторам и знакам пунктуации:
int foo = 1; int bar = 2; int num1 = foo+++++bar; // Error: treated as “foo++ ++ +baz”, not “foo++ + ++baz” int num2 = -----foo; // Error: treated as “-- -- -foo”, not “- -- --foo”
Правило максимального поглощения имеет следующие исключения:
- Токены препроцессора — имена заголовков — образуются только в следующих случаях:
- после токена препроцессора
includeв директиве #include
| (начиная с C++17) |
| (начиная с C++20) |
std::vector<int> x; // OK, “int” is not a header name
- Если следующие три символа —
<::, а следующий за ними символ не является ни:, ни>, то<рассматривается как отдельный токен препроцессора, а не как первый символ альтернативного токена<:.
struct Foo { static const int v = 1; };
std::vector<::Foo> x; // OK, <: not taken as the alternative token for [
extern int y<::>; // OK, same as “extern int y[];”
int z<:::Foo::value:>; // OK, same as “int z[::Foo::value];”
template<int i> class X { /* ... */ };
template<class T> class Y { /* ... */ };
Y<X<1>> x3; // OK, declares a variable “x3” of type “Y<X<1> >”
Y<X<6>>1>> x4; // Syntax error
Y<X<(6>>1)>> x5; // OK
#define R "x" const char* s = R"y"; // ill-formed raw string literal, not "x" "y" const char* s2 = R"(a)" "b)"; // a raw string literal followed by a normal string literal | (начиная с C++11) |
Токены
Токен — минимальный лексический элемент языка на этапе трансляции 7.
К категориям токенов относятся:
- идентификаторы
- ключевые слова
- литералы
- операторы и знаки пунктуации (кроме операторов препроцессора)
Этапы трансляции
Трансляция выполняется так, как если бы этапы проходили в порядке от 1-го до 9-го. Реализации ведут себя так, как если бы эти отдельные этапы выполнялись, хотя на практике разные этапы могут объединяться.
Этап 1: Отображение исходных символов
1) Отдельные байты файла с исходным кодом отображаются (способом, определяемым реализацией) в символы базового набора исходных символов. В частности, зависящие от ОС индикаторы конца строки заменяются символами новой строки. 2) Набор принимаемых символов исходного файла определяется реализацией(начиная с C++11). Любой символ исходного файла, который невозможно отобразить в символ базового набора исходных символов, заменяется его универсальным именем символа (экранированным с помощью \u или \U) либо некоторым эквивалентно обрабатываемым представлением, определяемым реализацией.
| (до C++23) | ||
| Гарантируется поддержка входных файлов, представляющих собой последовательность кодовых единиц UTF-8 (файлов UTF-8). Набор поддерживаемых входных файлов других типов определяется реализацией. Если этот набор не пуст, тип входного файла определяется способом, заданным реализацией, который включает возможность обозначать входные файлы как файлы UTF-8 независимо от их содержимого (распознавания маркера порядка байтов недостаточно).
| (начиная с C++23) |
Этап 2: Склеивание строк
\) стоит в конце строки (за ней непосредственно следуют ноль или более пробельных символов, кроме новой строки, а затем(начиная с C++23) символ новой строки), эти символы удаляются, объединяя две физические строки исходного кода в одну логическую строку. Эта операция выполняется за один проход; строка, заканчивающаяся двумя обратными косыми чертами, за которыми следует пустая строка, не объединяет три строки в одну.Этап 3: Лексический анализ
// The following #include directive can de decomposed into 5 preprocessing tokens: // punctuators (#, < and >) // │ // ┌────────┼────────┐ // │ │ │ #include <iostream> // │ │ // │ └── header name (iostream) // │ // └─────────── identifier (include)
// Error: partial string literal "abc
// Error: partial comment /* comment
При чтении символов исходного файла для формирования следующего токена препроцессора (то есть если они не входят в комментарий или другую форму пробельных символов) универсальные имена символов распознаются и заменяются соответствующим элементом набора символов трансляции, за исключением случаев, когда они входят в последовательность символов одного из следующих токенов препроцессора:
| (начиная с C++23) |
| 2) Все преобразования, выполненные на этапе 1 и(до C++23) этапе 2 между начальной и конечной двойными кавычками любого необработанного строкового литерала, отменяются. | (начиная с C++11) |
- Каждый комментарий заменяется одним пробелом.
- Символы новой строки сохраняются.
- Не определено, сохраняется ли каждая непустая последовательность пробельных символов, кроме символов новой строки, или заменяется одним пробелом.
Этап 4: Препроцессирование
Этап 5: Определение общей кодировки строковых литералов
| 1) Все символы в символьных литералах и строковых литералах преобразуются из исходной кодировки в кодировку (которая может быть многобайтовой, например UTF-8, при условии, что 96 символов базового набора символов представлены одним байтом). 2) Управляющие последовательности и универсальные имена символов в символьных литералах и обычных строковых литералах раскрываются и преобразуются в кодировку литерала. Если символ, заданный универсальным именем символа, не может быть закодирован одной кодовой точкой в соответствующей кодировке литерала, результат определяется реализацией, но гарантированно не является нулевым (широким) символом. | (до C++23) |
| Для последовательности из двух или более соседних токенов строковых литералов определяется общий префикс кодировки, как описано здесь. Затем считается, что у каждого такого токена строкового литерала есть этот общий префикс кодировки. (Преобразование символов перенесено на этап 3.) | (начиная с C++23) |
Этап 6: Конкатенация строковых литералов
Соседние строковые литералы конкатенируются.
Этап 7: Компиляция
Выполняется компиляция: каждый токен препроцессора преобразуется в токен. Токены подвергаются синтаксическому и семантическому анализу и преобразуются в единицу трансляции.
Этап 8: Инстанцирование шаблонов
Каждая единица трансляции анализируется для составления списка необходимых инстанцирований шаблонов, включая инстанцирования, запрошенные с помощью явных инстанцирований. Определения шаблонов обнаруживаются, а необходимые инстанцирования выполняются для создания единиц инстанцирования.
Этап 9: Компоновка
Единицы трансляции, единицы инстанцирования и компоненты библиотек, необходимые для разрешения внешних ссылок, объединяются в образ программы, содержащий сведения, необходимые для выполнения программы в её среде исполнения.
Примечания
Исходные файлы, единицы трансляции и транслированные единицы трансляции необязательно должны храниться в виде файлов; между этими сущностями и любым внешним представлением также необязательно должно существовать взаимно-однозначное соответствие. Описание носит концептуальный характер и не задаёт конкретную реализацию.
| В некоторых реализациях преобразование на этапе 5 можно контролировать с помощью параметров командной строки: gcc и clang используют | (до C++23) |
Некоторые компиляторы не реализуют единицы инстанцирования (также известные как репозитории шаблонов или реестры шаблонов) и просто компилируют каждое инстанцирование шаблона на этапе 7, сохраняя код в объектном файле, где оно запрошено неявно или явно, после чего компоновщик объединяет эти скомпилированные инстанцирования в одно на этапе 9.
Отчёты об ошибках стандарта
Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применён к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 787 | C++98 | поведение было неопределённым, если непустой исходный файл не заканчивался символом новой строки в конце этапа 2 | в этом случае добавлять завершающий символ новой строки |
| CWG 1104 | C++98 | альтернативный токен <: приводил к тому, что std::vector<::std::string>рассматривался как std::vector[:std::string> | добавлено дополнительное правило лексического анализа, чтобы учесть этот случай |
| CWG 1775 | C++11 | формирование универсального имени символа внутри необработанного строкового литерала на этапе 2 приводило к неопределённому поведению | поведение определено |
| CWG 2747 | C++98 | на этапе 2 после склеивания проверялось склеивание в конце файла; такая проверка не нужна | проверка удалена |
| P2621R3 | C++98 | универсальные имена символов нельзя было формировать посредством склеивания строк или конкатенации токенов | разрешено |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
- 5.2 Этапы трансляции [lex.phases]
- Стандарт C++20 (ISO/IEC 14882:2020):
- 5.2 Этапы трансляции [lex.phases]
- Стандарт C++17 (ISO/IEC 14882:2017):
- 5.2 Этапы трансляции [lex.phases]
- Стандарт C++14 (ISO/IEC 14882:2014):
- 2.2 Этапы трансляции [lex.phases]
- Стандарт C++11 (ISO/IEC 14882:2011):
- 2.2 Этапы трансляции [lex.phases]
- Стандарт C++03 (ISO/IEC 14882:2003):
- 2.1 Этапы трансляции [lex.phases]
- Стандарт C++98 (ISO/IEC 14882:1998):
- 2.1 Этапы трансляции [lex.phases]
См. также
| Документация по C о этапах трансляции |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/translation_phases