Я хочу широко использовать оператор ## — и магию enum для обработки огромного количества похожих операций доступа, обработки ошибок и потока данных.
При применении ##
а также #
Операторы препроцессора приводят к неверному pp-токену, поведение в Си не определено
Порядок работы препроцессора в целом не определен (*) в C90 (см. Оператор вставки токена). Теперь в некоторых случаях бывает (так сказано в разных источниках, в том числе в комитете MISRA и на указанной странице), что порядок нескольких операторов ## / # влияет на возникновение неопределенного поведения. Но мне очень трудно понять примеры этих источников и определить общее правило.
Итак, мои вопросы:
Каковы правила для действительных pp-токенов?
Есть ли разница между различными стандартами C и C ++?
Моя текущая проблема: является ли законным следующее со всеми 2 заказами оператора? (**)
#define test(A) test_## A ## _THING
int test(0001) = 2;
Комментарии:
(*) Я не использую «is undefined», потому что это не имеет ничего общего с неопределенным поведением, пока ИМХО, а скорее с неопределенным поведением. Более чем один ## или # применяемый оператор, как правило, не делает программу ошибочной. Очевидно, что есть порядок — мы просто не можем предсказать, какой — поэтому порядок не определен.
(**) Это не актуальная заявка на нумерацию. Но картина эквивалентна.
Они прописаны в соответствующих стандартах; C11 §6.4 и C ++ 11 §2.4. В обоих случаях они соответствуют производству Предварительная обработка-маркер. Помимо С.-номер, они не должны быть слишком удивительными. Остальные возможности — это идентификаторы (включая ключевые слова), «знаки препинания» (в C ++, Предварительная обработка-оп-или-Punc), строковые и символьные литералы и любые другие символы, не являющиеся пробелами, которые не соответствуют ни одной другой продукции.
За некоторыми исключениями, любая последовательность символов может быть разложена в последовательность действительных предобработки жетоны. (Единственное исключение — несоответствующие кавычки и апострофы: одиночная кавычка или апостроф не являются действительными Предварительная обработка-маркер, поэтому текст, содержащий неопределенную строку или символьный литерал, не может быть размечен.)
В контексте ##
оператор, однако, результат конкатенации должен быть одним Предварительная обработка-маркер. Таким образом, недопустимая конкатенация — это конкатенация, результатом которой является последовательность символов, которая содержит несколько предобработки жетоны.
Да, есть небольшие различия:
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.letter
включает в себя подчеркивание. Кроме того, можно использовать универсальные имена символов (за исключением последующего апострофа в C ++).После завершения предварительной обработки все С.-номер будут преобразованы в числовые литералы, если это возможно. Если преобразование невозможно (так как токен не соответствует синтаксису для любого числового литерала), программа недействительна.
#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 ++ (особенно недавно) имеет больше форм допустимых токенов предварительной обработки, таких как определяемые пользователем строковые литералы.