1.3 Токенизация ¶
После завершения текстовых преобразований входной файл преобразуется в последовательность токенов предварительной обработки. Они в основном соответствуют синтаксическим токенам, используемым компилятором C, но есть несколько различий. Разделителями токенов являются пробелы; сами по себе они не являются токенами. Токены не обязательно должны быть разделены пробелами, но часто это необходимо для избежания неоднозначностей.
В случае последовательности символов, имеющей более одной возможной токенизации, препроцессор действует жадно. Он всегда делает каждый токен, начиная слева, максимально возможным, прежде чем перейти к следующему токену. Например, a+++++b интерпретируется как a ++ ++ + b, а не как a ++ + ++ b, даже если последняя токенизация может быть частью корректной программы C, а первая — нет.
После того, как входной файл разбит на токены, границы токенов больше не изменяются, за исключением случаев использования оператора предварительной обработки ‘##’ для склеивания токенов. См. Конкатенацию. Например,
#define foo() bar
foo()baz
→ bar baz
not
→ barbazКомпилятор не повторно токенизирует выходные данные препроцессора. Каждый токен предварительной обработки становится одним токеном компилятора.
Токены предварительной обработки можно разделить на пять основных классов: идентификаторы, числа предварительной обработки, строковые литералы, пунктуаторы и прочие. Идентификатор такой же, как и идентификатор в C: любая последовательность букв, цифр или символов подчеркивания, начинающаяся с буквы или символа подчеркивания. Ключевые слова языка C не имеют значения для препроцессора; они являются обычными идентификаторами. Например, можно определить макрос, имя которого является ключевым словом. Единственный идентификатор, который можно считать ключевым словом предварительной обработки, — это defined. См. Определено.
Это в основном верно для других языков, использующих препроцессор C. Однако некоторые ключевые слова C++ имеют значение даже в препроцессоре. См. Имена операторов C++.
В стандарте C 1999 года идентификаторы могут содержать буквы, которые не входят в «базовый набор символов исходного текста», по усмотрению реализации (например, буквы с диакритическими знаками, греческие буквы или китайские иероглифы). Это можно сделать с расширенным набором символов или с помощью escape-последовательностей ‘\u’ и ‘\U’.
В качестве расширения GCC рассматривает ‘$’ как букву. Это делается для совместимости с некоторыми системами, такими как VMS, где ‘$’ обычно используется в именах системных функций и объектов. ‘$’ не является буквой в строгом соответствии со стандартом или если указан параметр -$. См. Вызов.
Число предварительной обработки имеет довольно странное определение. Эта категория включает все обычные целочисленные и вещественные константы, ожидаемые в C, а также ряд других элементов, которые первоначально могут не распознаваться как числа. Формально числа предварительной обработки начинаются с необязательной точки, обязательной цифры и затем продолжаются любой последовательностью букв, цифр, символов подчеркивания, точек и экспоненциальных выражений. Экспоненциальные выражения — это двухсимвольные последовательности ‘e+’, ‘e-’, ‘E+’, ‘E-’, ‘p+’, ‘p-’, ‘P+’ и ‘P-’. (Экспоненциальные выражения, начинающиеся с ‘p’ или ‘P’, используются для шестнадцатеричных вещественных констант.)
Цель этого необычного определения — изолировать препроцессор от полной сложности числовых констант. Он не должен различать лексически правильные и неправильные вещественные числа, что является сложной задачей. Это определение также позволяет разбить идентификатор в любой точке и получить ровно два токена, которые затем можно склеить обратно с помощью оператора ‘##’.
Возможны случаи, когда числа предварительной обработки могут привести к неправильной интерпретации программ. Например, 0xE+12 — это число предварительной обработки, которое не преобразуется в корректную числовую константу, поэтому возникает синтаксическая ошибка. Оно не означает 0xE + 12, что вы, возможно, имели в виду.
Строковые литералы — это строковые константы, символьные константы и имена файлов заголовков (аргумент ‘#include’).2 Строковые и символьные константы просты: "…" или '…'. В обоих случаях вложенные кавычки следует экранировать обратной косой чертой: '\'' — это символьная константа для ‘'’.
Нет ограничения на длину символьной константы, но значение символьной константы, содержащей более одного символа, определяется реализацией. См. Подробности реализации.
Имена файлов заголовков либо похожи на строковые константы, "…", либо записываются с использованием угловых скобок вместо них, <…>. В обоих случаях обратная косая черта является обычным символом. Нет способа экранировать закрывающие кавычки или угловые скобки. Препроцессор ищет файл заголовка в разных местах в зависимости от используемой формы. См. Операция включения.
Ни один строковый литерал не может выходить за пределы строки. Можно использовать продолженные строки или конкатенацию строковых констант.
Пунктуаторы — это все обычные знаки препинания, имеющие смысл в C и C++. Все символы препинания в ASCII, за исключением трёх, являются пунктуаторами C. Исключение составляют ‘@’, ‘$’ и ‘`’. Кроме того, все двух- и трёхсимвольные операторы являются пунктуаторами. Также существуют шесть диграфов, которые стандарт C++ называет альтернативными токенами, представляющими собой просто альтернативные способы написания других пунктуаторов. Это вторая попытка обойти отсутствие знаков препинания в устаревших системах. В отличие от триграфов, это не имеет отрицательных последствий, но не охватывает такой большой объём. Диграфы и соответствующие обычные пунктуаторы:
Digraph: <% %> <: :> %: %:%:
Punctuator: { } [ ] # ##
Любой другой одиночный байт считается «прочим» и передаётся на выход препроцессора без изменений. Компилятор C почти наверняка отклонит исходный код, содержащий токены «прочие». В ASCII «прочие» символы — это ‘@’, ‘$’, ‘`’ и управляющие символы, кроме NUL (все биты равны нулю). (Обратите внимание, что ‘$’ обычно рассматривается как буква.) Все байты с установленным старшим битом (числовой диапазон 0x7F–0xFF), которые не были успешно интерпретированы как часть расширенного символа в кодировке ввода, также являются «прочими» в данной реализации.
NUL — это особый случай из-за высокой вероятности того, что его появление является случайным, и потому, что он может быть невидимым для пользователя (многие терминалы вообще не отображают NUL). В комментариях NUL игнорируются молча, как и любой другой символ. В тексте NUL рассматривается как пробел. Например, эти две директивы имеют одинаковое значение.
#define X^@1 #define X 1
(где ‘^@’ — ASCII NUL). В строковых или символьных константах NUL сохраняются. В последнем двух случаях препроцессор выводит сообщение об ошибке.
Примечания
(2)
В стандарте C термин строковый литерал используется только для обозначения того, что мы называем строковыми константами.
© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-10.5.0/cpp/Tokenization.html