#c #concatenation #c-preprocessor #token-pasting-operator
Вопрос:
Я пишу свой собственный C-препроцессор на основе GCC. До сих пор это почти идентично, но я считаю излишним выполнять любую форму проверки токенов, объединяемых вирусом ##
. Итак, в моем руководстве по препроцессору я написал следующее:
3.5 Объединение
…
GCC запрещает объединение с двумя несовместимыми токенами предварительной обработки, такими как «x» и » » (в любом порядке). Это приведет к следующей ошибке: «вставка «x» и » » не дает допустимого токена предварительной обработки», однако это неверно для этого препроцессора — объединение может произойти между любым токеном.
Мои рассуждения просты: если он расширится до недопустимого кода, то компилятор выдаст ошибку, и поэтому мне не нужно явно обрабатывать такие случаи, что замедляет работу препроцессора и увеличивает сложность кода. Если это приведет к правильному коду, то это удаление ограничений просто сделает его более гибким (хотя, вероятно, в редких случаях).
Поэтому я хотел бы спросить, почему на самом деле происходит эта ошибка, почему на самом деле применяется это ограничение и является ли настоящим преступлением, если я откажусь от него в своем препроцессоре?
Комментарии:
1. Что означает
x
единый токен? Это недопустимый токен в C.2. Это не является допустимым токеном как таковым, но, тем не менее, он может сформировать допустимый код, нет? В любом случае, вопрос в том, действительно ли это ограничение так необходимо и почему.
3. Он может сформировать действительный код, если вы разделите его на два токена (x и ). Но как отдельный токен он недействителен.
4.
int x = 0; int y = *x *1
разве это не действительный код? Должно быть, я немного запутался, наверное, потому, что мой английский далек от совершенства 🙂5. Согласно спецификации, это неопределенное поведение, поэтому диагностика не требуется, и все, что вы делаете, является разумным. Разбиение его обратно на два токена является наиболее очевидным выбором, кроме выдачи ошибки. Если ваш препроцессор генерирует поток символов, а не поток токенов (поэтому компилятор перезапускает), это произойдет естественным образом
Ответ №1:
Что касается ISO C, если ##
создается недопустимый маркер, поведение не определено. Но здесь возникает несколько странная ситуация, заключающаяся в следующем. Вывод этапов перевода предварительной обработки языка C представляет собой поток токенов предварительной обработки (pp-токенов). Они преобразуются в токены, а затем синтаксически и семантически анализируются. Теперь вот важное правило: это нарушение ограничений, если pp-токен не имеет формы, позволяющей преобразовать его в токен. Таким образом, токен препроцессора, представляющий собой мусор, который вы пишете самостоятельно без помощи ##
оператора, должен быть диагностирован на предмет плохого лексического синтаксиса. Но если вы используете ##
для создания неверного маркера предварительной обработки, поведение не определено.
Обратите внимание на тонкость: поведение ##
не определено, если оно используется для создания неправильного маркера предварительной обработки. Это не тот случай, когда вставка четко определена, а затем поймана на этапе, когда pp-токены преобразуются в токены: она не определена с того момента, когда ##
оценивается.
В принципе, это историческое событие. Препроцессоры C исторически были (и, вероятно, некоторые из них) отдельными программами, с лексическим анализом, который отличался и был более свободным от компилятора нижестоящего уровня. Стандарт C попытался каким-то образом отразить это в терминах одного языка с этапами перевода, и в результате появились некоторые причуды и области, возможно, удивительной недоработки. (Например, на этапах предварительной обработки перевода числовой токен («pp-число») представляет собой странную лексическую грамматику, которая допускает тарабарщину, такую как токены с несколькими E
показателями с плавающей запятой.)
А теперь вернемся к вашей ситуации. Ваш текстовый препроцессор C фактически не выводит объекты pp-токенов; он выводит другой текстовый поток. У вас могут быть объекты pp-токенов внутри, но они сглаживаются при выводе. Таким образом, вы можете подумать, почему бы не позволить вашему ##
оператору просто слепо склеить любые два токена? В результате получается так, как если бы эти токены были сброшены в выходной поток без каких-либо промежуточных пробелов. (И это, вероятно , все, что было в ранних препроцессорах, которые поддерживались ##
и запускались как отдельные программы).
К сожалению, это означает, что ваш ##
оператор не является чисто добросовестным оператором вставки токенов; это просто оператор слепого сопоставления, который иногда создает один токен, когда происходит сопоставление двух токенов, которые будут лексически проанализированы как один нижестоящим компилятором. Если вы пойдете по этому пути, возможно, будет лучше быть честным и задокументировать это как таковое, а не изображать это как функцию гибкости.
С другой стороны, веской причиной для отклонения плохих токенов предварительной обработки в ##
операторе является обнаружение ситуаций, в которых он не может выполнить свое документированное описание работы: требование создания одного токена из двух. Идея заключается в том, что программист знает спецификацию языка (контракт между программистом и реализацией) и знает, что ##
предполагается создать один токен, и полагается на это. Для такого программиста любая ситуация, связанная с плохой вставкой токенов, является ошибкой, и этот программист лучше всего поддерживается диагностикой.
Разработчики GCC и препроцессора GNU CPP, вероятно, придерживались этой точки зрения: препроцессор-это не гибкий инструмент для редактирования текста, а часть цепочки инструментов, поддерживающих дисциплинированное программирование на языке Си.
Более того, неопределенное поведение задания вставки плохих токенов легко диагностируется, так почему бы не диагностировать его? Отсутствие требования к диагностике в этой области в стандарте выглядит просто исторической уступкой. Это своего рода «низко висящий плод» диагностики. Пусть те неопределенные формы поведения остаются недиагностированными, для которых диагностика трудна или трудноразрешима или требует штрафных санкций во время выполнения.
Комментарии:
1. Спасибо, что поделился таким объемом знаний и убедительными доводами. Да, я сканирую текст на лексическом уровне (символ за символом), используя свою огромную (также очень мощную) библиотеку синтаксического анализа, и главная цель-быть быстрым, потому что он будет частью интерпретатора языка Си. То, как он разработан в настоящее время, делает большую работу в этом отношении. Однако интерфейс будет ожидать достаточного сходства с GCC PP и предполагать токены, поэтому я, конечно, задокументирую, как анализируются входные данные, и
##
в результате это будет вести себя по-другому.2. Я бы все равно подумал о диагностике плохой вставки токенов, но я не думаю, что это «легко диагностируется» в первую очередь потому, что у меня нет правил для черно-белых токенов, но из того, что я знаю, независимо от этого потребуется синтаксический анализ, что уже делает его несколько дорогостоящим во всем понимании.
3. @Edenia Я подозреваю, что есть короткие пути, которые вы можете выбрать. Если вы определили категорию pp-токенов как перечисление, вы можете переключить тип двух токенов на различные случаи, для которых, скорее всего, потребуется в лучшем случае посмотреть на последний символ левого токена и первый символ правого или что-то в этом роде. Например,
identifier ## punctuator
это случай мгновенного сбоя, как и его зеркальное отражение.4. Как один из авторов препроцессора GCC, я могу подтвердить вашу догадку о том, что мы рассматривали препроцессор как «часть цепочки инструментов, поддерживающих дисциплинированное программирование на C». Это также было преднамеренное дизайнерское решение, чтобы сделать жесткие ошибки во время компиляции из как можно большего числа случаев неопределенного поведения во время предварительной обработки. Наконец, препроцессор GCC интегрирован с компилятором, поэтому он создает поток объектов pp-токенов, а не текст (
-E
режим обратного преобразования в текст). В этом контексте естественно диагностировать плохие pp-токены при создании.5. @zwol Поздравляет вас с вашей работой 🙂 За свои 11 лет работы я никогда не находил разработку программы проблематичной и никогда не оспаривал детали ее реализации. Я использую
-E
его в тестовых целях, чтобы сравнить предварительно обработанный код с моей версией. Также в моей программе, которая проверяет его и сравнивает автоматически, собирая данные.