Spec-Zone.ru › C++

Этапы трансляции

Исходные файлы C++ обрабатываются компилятором для создания программ на C++.

Процесс трансляции

Текст программы на C++ хранится в единицах, называемых исходными файлами.

Исходные файлы C++ проходят трансляцию, в результате которой получается единица трансляции, состоящая из следующих этапов:

  1. Каждый исходный файл преобразуется в последовательность символов.
  2. Каждая последовательность символов преобразуется в последовательность токенов препроцессора, разделённых пробельными символами.
  3. Каждый токен препроцессора преобразуется в токен, образуя последовательность токенов.
  4. Каждая последовательность токенов преобразуется в единицу трансляции.

Программа на C++ может быть сформирована из транслированных единиц трансляции. Единицы трансляции могут транслироваться отдельно, а затем связываться для создания исполняемой программы.

Описанный выше процесс можно разделить на 9 этапов трансляции.

Токены препроцессора

Токен препроцессора — минимальный лексический элемент языка на этапах трансляции с 3-го по 6-й.

К категориям токенов препроцессора относятся:

  • имена заголовков (например, <iostream> или "myfile.h")
  • токены-заполнители, создаваемые препроцессором для директив import и module (то есть import XXX; и module XXX;)
(начиная с 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
  • в выражении __has_include
(начиная с C++17)
  • после токена препроцессора import в директиве import
(начиная с 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) либо некоторым эквивалентно обрабатываемым представлением, определяемым реализацией.
3) Триграфные последовательности заменяются соответствующими односивольными представлениями. (до C++17)
(до C++23)

Гарантируется поддержка входных файлов, представляющих собой последовательность кодовых единиц UTF-8 (файлов UTF-8). Набор поддерживаемых входных файлов других типов определяется реализацией. Если этот набор не пуст, тип входного файла определяется способом, заданным реализацией, который включает возможность обозначать входные файлы как файлы UTF-8 независимо от их содержимого (распознавания маркера порядка байтов недостаточно).

  • Если входной файл определён как файл UTF-8, он должен представлять собой корректную последовательность кодовых единиц UTF-8, которая декодируется в последовательность скалярных значений Unicode. Затем формируется последовательность элементов набора символов трансляции, в которой каждое скалярное значение Unicode отображается в соответствующий элемент набора символов трансляции. В полученной последовательности каждая пара символов входной последовательности, состоящая из возврата каретки (U+000D) с последующим переводом строки (U+000A), а также каждый символ возврата каретки (U+000D), за которым непосредственно не следует перевод строки (U+000A), заменяются одним символом новой строки.
  • Для любого другого типа входного файла, поддерживаемого реализацией, символы отображаются (способом, определяемым реализацией) в последовательность элементов набора символов трансляции. В частности, зависящие от ОС индикаторы конца строки заменяются символами новой строки.
(начиная с C++23)

Этап 2: Склеивание строк

1) Если первый символ трансляции — маркер порядка байтов (U+FEFF), он удаляется. (начиная с C++23)Если обратная косая черта (\) стоит в конце строки (за ней непосредственно следуют ноль или более пробельных символов, кроме новой строки, а затем(начиная с C++23) символ новой строки), эти символы удаляются, объединяя две физические строки исходного кода в одну логическую строку. Эта операция выполняется за один проход; строка, заканчивающаяся двумя обратными косыми чертами, за которыми следует пустая строка, не объединяет три строки в одну.
2) Если непустой исходный файл после этого этапа не заканчивается символом новой строки (к этому моменту обратные косые черты в конце строки уже не участвуют в склеивании), в конец добавляется завершающий символ новой строки.

Этап 3: Лексический анализ

1) Исходный файл разбивается на токены препроцессора и пробельные символы:
// 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-char-sequence)
  • строковый литерал (s-char-sequence и r-char-sequence), за исключением разделителей (d-char-sequence)
  • имя заголовка (h-char-sequence и q-char-sequence)
(начиная с C++23)
2) Все преобразования, выполненные на этапе 1 и(до C++23) этапе 2 между начальной и конечной двойными кавычками любого необработанного строкового литерала, отменяются. (начиная с C++11)
3) Пробельные символы преобразуются следующим образом:
  • Каждый комментарий заменяется одним пробелом.
  • Символы новой строки сохраняются.
  • Не определено, сохраняется ли каждая непустая последовательность пробельных символов, кроме символов новой строки, или заменяется одним пробелом.

Этап 4: Препроцессирование

1) Выполняется препроцессор.
2) Каждый файл, подключённый директивой #include, рекурсивно проходит этапы с 1-го по 4-й.
3) В конце этого этапа из исходного кода удаляются все директивы препроцессора.

Этап 5: Определение общей кодировки строковых литералов

1) Все символы в символьных литералах и строковых литералах преобразуются из исходной кодировки в кодировку (которая может быть многобайтовой, например UTF-8, при условии, что 96 символов базового набора символов представлены одним байтом). 2) Управляющие последовательности и универсальные имена символов в символьных литералах и обычных строковых литералах раскрываются и преобразуются в кодировку литерала.

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

(до C++23)

Для последовательности из двух или более соседних токенов строковых литералов определяется общий префикс кодировки, как описано здесь. Затем считается, что у каждого такого токена строкового литерала есть этот общий префикс кодировки. (Преобразование символов перенесено на этап 3.)

(начиная с C++23)

Этап 6: Конкатенация строковых литералов

Соседние строковые литералы конкатенируются.

Этап 7: Компиляция

Выполняется компиляция: каждый токен препроцессора преобразуется в токен. Токены подвергаются синтаксическому и семантическому анализу и преобразуются в единицу трансляции.

Этап 8: Инстанцирование шаблонов

Каждая единица трансляции анализируется для составления списка необходимых инстанцирований шаблонов, включая инстанцирования, запрошенные с помощью явных инстанцирований. Определения шаблонов обнаруживаются, а необходимые инстанцирования выполняются для создания единиц инстанцирования.

Этап 9: Компоновка

Единицы трансляции, единицы инстанцирования и компоненты библиотек, необходимые для разрешения внешних ссылок, объединяются в образ программы, содержащий сведения, необходимые для выполнения программы в её среде исполнения.

Примечания

Исходные файлы, единицы трансляции и транслированные единицы трансляции необязательно должны храниться в виде файлов; между этими сущностями и любым внешним представлением также необязательно должно существовать взаимно-однозначное соответствие. Описание носит концептуальный характер и не задаёт конкретную реализацию.

В некоторых реализациях преобразование на этапе 5 можно контролировать с помощью параметров командной строки: gcc и clang используют -finput-charset для указания кодировки исходного набора символов, -fexec-charset и -fwide-exec-charset — для указания кодировок обычных и широких литералов соответственно, а Visual Studio 2015 Update 2 и более поздние версии используют /source-charset и /execution-charset для указания исходной кодировки и кодировки литералов соответственно.

(до 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API