Именно для того, чтобы избежать альтернативных вариантов написания.
Основными причинами добавления универсальных имен символов (UCN) в C и C++ были:
разрешить идентификаторам включать буквы за пределами основного исходного набора символов (например, ñ
).
позволяют переносимые механизмы для записи строковых и символьных литералов, которые включают символы, не входящие в базовый исходный набор символов.
Кроме того, было желание, чтобы изменения в существующих компиляторах были как можно более ограниченными, и, в частности, чтобы компиляторы (и другие инструменты) могли продолжать использовать свои установленные (и часто сильно оптимизированные) функции лексического анализа.
Это было непросто, потому что существуют огромные различия в архитектуре лексического анализа разных компиляторов. Не вдаваясь во все детали, оказалось, что возможны две широкие стратегии реализации:
Компилятор может внутри использовать какую-то одну универсальную кодировку, такую как UTF-8. Все входные файлы в других кодировках будут транскрибироваться в эту внутреннюю кодировку на самом раннем этапе конвейера ввода. Кроме того, UCN (где бы они ни появлялись) будут преобразованы в соответствующую внутреннюю кодировку. Это последнее преобразование можно проводить параллельно с обработкой строки продолжения, которая также требует обнаружения обратной косой черты, что позволяет избежать дополнительной проверки каждого входного символа на условие, которое очень редко оказывается истинным.
Компилятор может внутри использовать строгий (7-битный) ASCII. Входные файлы в кодировках, допускающих другие символы, будут транскрибированы в ASCII, а символы, отличные от ASCII, будут преобразованы в UCN до любого другого лексического анализа.
По сути, обе эти стратегии будут реализованы в фазе 1 (или эквивалентной), то есть задолго до того, как будет проведен лексический анализ. Но обратите внимание на разницу: стратегия 1 преобразует UCN во внутреннюю кодировку символов, а стратегия 2 преобразует непредставимые символы в UCN.
Общим для этих двух стратегий является то, что после завершения транскрипции больше нет никакой разницы между символом, введенным непосредственно в исходный поток (в любой кодировке, используемой исходным файлом), и символом, описанным с помощью UCN. Таким образом, если компилятор разрешает исходные файлы UTF-8, вы можете ввести ñ
либо как два байта 0xc3, 0xb1, либо как шестисимвольную последовательность \u00D1
, и они оба будут иметь одну и ту же последовательность байтов. Это, в свою очередь, означает, что каждый идентификатор имеет только одно написание, поэтому не требуется никаких изменений (например) для поиска в таблице символов.
Как правило, компиляторы просто передают имена переменных через конвейер компиляции, оставляя их для обработки ассемблерам или компоновщикам. Если эти последующие инструменты не принимают расширенные кодировки символов или UCN (в зависимости от стратегии реализации), то имена, содержащие такие символы, должны быть искажены (транскрибированы), чтобы сделать их приемлемыми. Но даже если это необходимо, это незначительное изменение, и его можно выполнить в четко определенном интерфейсе.
Вместо того, чтобы разрешать споры между поставщиками компиляторов, чьи продукты (или группы разработчиков) имели явные предпочтения между двумя стратегиями, комитеты по стандартам C и C++ выбрали механизмы и ограничения, которые делают обе стратегии совместимыми. В частности, оба комитета запрещают использование UCN, представляющих символы, которые уже имеют кодировку в базовом исходном наборе символов. Это позволяет избежать таких вопросов, как:
Что произойдет, если я поставлю \u0022
внутри строкового литерала:
const char* quote = "\u0022";
Если компилятор преобразует UCN в символы, которые они представляют, то к тому времени, когда лексический анализатор увидит эту строку, "\u0022"
будет преобразовано в """
, что является лексической ошибкой. С другой стороны, компилятор, который сохраняет UCN до конца, с радостью примет это как строковый литерал. Запрет на использование UCN, представляющего собой кавычки, позволяет избежать этой возможной непереносимости.
Точно так же будет ли '\u005cn'
символом новой строки? Опять же, если UCN преобразуется в обратную косую черту на этапе 1, то на этапе 3 строковый литерал определенно будет рассматриваться как новая строка. Но если UCN преобразуется в символьное значение только после того, как токен символьного литерала был идентифицирован как таковой, то результирующий символьный литерал будет содержать два символа (значение, определяемое реализацией).
А как же 2 \u002B 2
? Будет ли это выглядеть как дополнение, хотя UCN не должны использоваться для знаков препинания? Или это будет идентификатор, начинающийся с небуквенного кода?
И так далее, по большому количеству подобных вопросов.
Всех этих подробностей можно избежать благодаря простому приему, требующему, чтобы UCN не могли использоваться для написания символов в основном исходном наборе символов. И это то, что было воплощено в стандартах.
Обратите внимание, что базовый исходный набор символов не содержит всех символов ASCII. Он не содержит большинства управляющих символов и не содержит символов ASCII $
, @
и `
. Эти символы (которые не имеют значения в программе C или C++ за пределами строковых и символьных литералов) могут быть записаны как UCN \u0024
, \u0040
и \u0060
соответственно.
Наконец, чтобы увидеть, какие узлы вам нужно развязать, чтобы правильно лексически анализировать C (или C++), рассмотрим следующий фрагмент:
const char* s = "\\
n";
Поскольку строки продолжения рассматриваются на этапе 1, до лексического анализа, а этап 1 ищет только двухсимвольную последовательность, состоящую из обратной косой черты, за которой следует новая строка, эта строка аналогична
const char* s = "\n";
Но это могло быть неочевидно, глядя на исходный код.
person
rici
schedule
06.07.2020