Каковы определения действительных и недействительных pp-токенов?

Я хочу широко использовать оператор ## — и магию enum для обработки огромного количества похожих операций доступа, обработки ошибок и потока данных.

При применении ## а также # Операторы препроцессора приводят к неверному pp-токену, поведение в Си не определено

Порядок работы препроцессора в целом не определен (*) в C90 (см. Оператор вставки токена). Теперь в некоторых случаях бывает (так сказано в разных источниках, в том числе в комитете MISRA и на указанной странице), что порядок нескольких операторов ## / # влияет на возникновение неопределенного поведения. Но мне очень трудно понять примеры этих источников и определить общее правило.

Итак, мои вопросы:

  1. Каковы правила для действительных pp-токенов?

  2. Есть ли разница между различными стандартами C и C ++?

  3. Моя текущая проблема: является ли законным следующее со всеми 2 заказами оператора? (**)

    #define test(A) test_## A ## _THING
    int test(0001) = 2;
    

Комментарии:

(*) Я не использую «is undefined», потому что это не имеет ничего общего с неопределенным поведением, пока ИМХО, а скорее с неопределенным поведением. Более чем один ## или # применяемый оператор, как правило, не делает программу ошибочной. Очевидно, что есть порядок — мы просто не можем предсказать, какой — поэтому порядок не определен.

(**) Это не актуальная заявка на нумерацию. Но картина эквивалентна.

3

Решение

Какие правила действительны PP-маркеры?

Они прописаны в соответствующих стандартах; C11 §6.4 и C ++ 11 §2.4. В обоих случаях они соответствуют производству Предварительная обработка-маркер. Помимо С.-номер, они не должны быть слишком удивительными. Остальные возможности — это идентификаторы (включая ключевые слова), «знаки препинания» (в C ++, Предварительная обработка-оп-или-Punc), строковые и символьные литералы и любые другие символы, не являющиеся пробелами, которые не соответствуют ни одной другой продукции.

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

В контексте ## оператор, однако, результат конкатенации должен быть одним Предварительная обработка-маркер. Таким образом, недопустимая конкатенация — это конкатенация, результатом которой является последовательность символов, которая содержит несколько предобработки жетоны.

Есть ли различия между C и C ++?

Да, есть небольшие различия:

  • C ++ имеет пользовательские строковые и символьные литералы и допускает «необработанные» строковые литералы. Эти литералы будут размечены по-разному в C, поэтому они могут быть множественными предобработки жетоны или (в случае необработанных строковых литералов) даже недопустимый предобработки жетоны.

  • C ++ включает символы ::, .* а также ->*все они будут помечены как два punctuator токены в C. Кроме того, в C ++ некоторые вещи выглядят как ключевые слова (например, new, delete) являются частью Предварительная обработка-оп-или-Punc (хотя эти символы действительны preprocssing жетоны на обоих языках.)

  • C допускает шестнадцатеричные литералы с плавающей точкой (например, 1.1p-3), которые не действительны предобработки жетоны в C ++.

  • C ++ позволяет использовать апострофы в целочисленных литералах в качестве разделителей (1'000'000'000). В C это, вероятно, приведет к непревзойденным апострофам.

  • Существуют небольшие различия в обработке имен универсальных символов (например, \u0234).

  • В C ++ <:: будет помечен как <, :: если это не сопровождается : или же >, (<::: а также <::> обычно токенизируются с использованием правила наибольшего совпадения.) В C нет исключений из правила самого длинного совпадения; <:: всегда токенизируется с использованием правила наибольшего совпадения, поэтому первый токен всегда будет <:,

Законно ли объединять test_, 0001, а также _THING, хотя порядок объединения не указан?

Да, это законно на обоих языках.

test_ ## 0001 => test_0001             (identifier)
test_0001 ## _THING => test_0001_THING (identifier)

0001 ## _THING => 0001_THING           (pp-number)
test_ ## 0001_THING => test_0001_THING (identifier)

Каковы примеры неправильной конкатенации токенов?

Предположим, у нас есть

#define concat3(a, b, c) a ## b ## c

Теперь следующие значения недопустимы независимо от порядка объединения:

concat3(., ., .)

.. это не знак, хотя ... является. Но объединение должно продолжаться в некотором порядке, и .. будет необходимым промежуточным значением; поскольку это не один токен, конкатенация будет недействительной.

concat3(27,e,-7)

Вот, -7 это два токена, поэтому он не может быть объединен.

И вот случай, в котором порядок конкатенации имеет значение:

concat3(27e, -, 7)

Если это объединено слева направо, это приведет к 27e- ## 7, который является объединением двух pp-чисел. Но - не может быть соединен с 7потому что (как указано выше) -7 это не один токен.

Что именно С.-номер?

В общих чертах, С.-номер это расширенный набор токенов, которые могут быть преобразованы в (одиночные) числовые литералы или могут быть недействительными. Определение является преднамеренно широким, частично для того, чтобы разрешить (некоторые) конкатенации токенов и частично для того, чтобы изолировать препроцессор от периодических изменений в числовых форматах. Точное определение можно найти в соответствующих стандартах, но неофициально маркер С.-номер если:

  • Начинается с десятичной цифры или точки (.) с последующим десятичным знаком.
  • Остальная часть токена — это буквы, цифры и точки, возможно, включая символы знака (+, -) если ему предшествует символ степени. Символ экспоненты может быть E или же e на обоих языках; а также P а также p в C с C99.
  • В C ++ С.-номер также может включать (но не начинаться с) апостроф, за которым следует буква или цифра.
  • Примечание: выше, letter включает в себя подчеркивание. Кроме того, можно использовать универсальные имена символов (за исключением последующего апострофа в C ++).

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

5

Другие решения

#define test(A) test_## A ## _THING
int test(0001) = 2;

Это допустимо как для оценки LTR, так и для оценки RTL, поскольку оба test_0001 а также 0001_THING действительны токены препроцессора. Первый является идентификатором, а второй — числом pp; числа pp не проверяются на правильность суффикса до более поздней стадии компиляции; думаю, например 0001u беззнаковый восьмеричный литерал.

Несколько примеров, чтобы показать, что порядок оценки делает иметь значение:

#define paste2(a,b) a##b
#define paste(a,b) paste2(a,b)
#if defined(LTR)
#define paste3(a,b,c) paste(paste(a,b),c)
#elif defined(RTL)
#define paste3(a,b,c) paste(a,paste(b,c))
#else
#define paste3(a,b,c)  a##b##c
#endif
double a = paste3(1,.,e3), b = paste3(1e,+,3);  // OK LTR, invalid RTL

#define stringify2(x) #x
#define stringify(x) stringify2(x)
#define stringify_paste3(a,b,c) stringify(paste3(a,b,c))
char s[] = stringify_paste3(%:,%,:);            // invalid LTR, OK RTL

Если ваш компилятор использует последовательный порядок оценки (LTR или RTL) а также выдает ошибку при генерации неверного pp-токена, тогда именно одна из этих строк выдаст ошибку. Естественно, слабый компилятор вполне может разрешить и то и другое, в то время как строгий компилятор может не разрешить ни того, ни другого.

Второй пример довольно надуманный; из-за способа построения грамматики очень трудно найти pp-токен, который действителен при сборке RTL, но не при сборке LTR.

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

0

По вопросам рекламы [email protected]