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 года идентификаторы могут содержать буквы, которые не входят в «базовый набор символов исходного текста», по усмотрению реализации (например, буквы с диакритическими знаками, греческие буквы или китайские иероглифы). Это можно сделать с помощью расширенного набора символов или эскейп-последовательностей «\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-14.2.0/cpp/Tokenization.html