C конкатенация препроцессора вне #define
Мне было интересно, почему мы не можем использовать конкатенацию маркера вне define
s.
Это происходит, когда я хочу их в одно и то же время:
- конфликтующее именование в библиотеке (или для "generics" )
- debugability; при использовании
define
для этого весь код объединяется в строку, и отладчик будет показывать только строку, в которой был использован define
Некоторым людям может понадобиться пример (актуальный вопрос ниже):
lib.inc:
#ifndef NAME
#error includer should first define NAME
#endif
void NAME() { // works
}
// void NAME##Init() { // doesn't work
// }
main.c:
#define NAME conflictfree
#include "lib.inc"
int main(void) {
conflictfree();
// conflictfreeInit();
return 0;
}
Ошибка:
In file included from main.c:2:0:
lib.h:6:10: error: stray '##' in program
void NAME##Init();
^
Эмпирическое правило "concat only in define". И если я правильно помню: причина - из-за фаз препроцессора.
Вопрос: Почему он не работает. Аргумент фаз звучит так, как будто это было ограничение реализации (а не логическая причина), а затем нашло свое отражение в стандарте. Что может быть так сложно в принятии NAME##Init()
, если NAME()
отлично работает?
Ответы
Ответ 1
В разделе 3.8.3.3 документа "Обоснование ANSI C" объясняется аргументация оператора ##
. Один из основных принципов гласит:
Формальный параметр (или нормальный операнд) в качестве операнда для ## не расширяется до вставки.
Это означает, что вы получите следующее:
#define NAME foo
void NAME##init(); // yields "NAMEinit", not "fooinit"
Это делает его бесполезным в этом контексте и объясняет, почему вы должны использовать два уровня макроса для конкатенации чего-либо, хранящегося в макросе. Простое изменение оператора, чтобы всегда разворачивать операнды, не было бы идеальным решением, потому что теперь вы не сможете (в этом примере) также объединиться с явной строкой "NAME
", если хотите; он всегда будет расширяться до значения макроса.
Ответ 2
Почему это был непростой вопрос. Может быть, пришло время спросить у стандартного комитета, почему они были такими же сумасшедшими, как и стандартизация (теперь удаленная) функция gets()
?
Иногда, стандарт просто мозг мертв, хотим мы этого или нет. Первый C не был сегодня C. Он не был "разработан" сегодня C, но "вырос" в нем. Это привело к немало несогласованности и недостаткам дизайна на дороге. Было бы совершенно справедливо разрешить ##
в непрямых строках, но опять же, C был выращен, а не построен. И пусть не начнет говорить о последствиях, которые та же самая модель привнесла в С++...
В любом случае, мы не здесь, чтобы прославлять стандарты, поэтому один из способов обойти это следует. Прежде всего, в lib.inc
...
#include <stdio.h>
#ifndef NAME
#error Includer should first define 'NAME'!
#endif
// We need 'CAT_HELPER' because of the preprocessor expansion rules
#define CAT_HELPER(x, y) x ## y
#define CAT(x, y) CAT_HELPER(x, y)
#define NAME_(x) CAT(NAME, x)
void NAME(void)
{
printf("You called %s(), and you should never do that!\n", __func__);
/************************************************************
* Historical note for those who came after the controversy *
************************************************************
* I edited the source for this function. It 100% safe now.
* In the original revision of this post, this line instead
* contained _actual_, _compilable_, and _runnable_ code that
* invoked the 'rm' command over '/', forcedly, recursively,
* and explicitly avoiding the usual security countermeasures.
* All of this under the effects of 'sudo'. It was a _bad_ idea,
* but hopefully I didn't actually harm anyone. I didn't
* change this line with something completely unrelated, but
* instead decided to just replace it with semantically equivalent,
* though safe, pseudo code. I never had malicious intentions.
*/
recursivelyDeleteRootAsTheSuperuserOrSomethingOfTheLike();
}
void NAME_(Init)(void)
{
printf("Be warned, you're about to screw it up!\n");
}
Затем в main.c
...
#define NAME NeverRunThis
#include "lib.inc"
int main() {
NeverRunThisInit();
NeverRunThis();
return 0;
}
Ответ 3
В то время как большая часть языка C развивалась и развивалась до его стандартизации, ##
был изобретен комитетом C89, так что действительно они могли бы использовать и другой подход. Я не экстрасенс, поэтому я не могу сказать, что почему стандартный комитет C89 решил стандартизировать токен, вставляя именно то, что он сделал, но в ANSI C Rationale 3.8.3.3 говорится, что "принципы [его дизайна] черты предшествующего уровня техники, и соответствуют спецификации оператора стробирования."
Но изменение стандарта так, чтобы X ## Y
было разрешено за пределами тела макроса, было бы не очень полезно в вашем случае: X
или Y
не были бы расширены до того, как ##
будет применен в макросе тела, так что даже если бы было возможно иметь NAME ## Init
для предполагаемых результатов вне тела макроса, необходимо было бы изменить семантику ##
. Если бы семантика не изменилась, вам все равно понадобилось бы косвенное. И единственный способ получить эту косвенность - это использовать его в пределах тела макроса!
Препроцессор C уже позволяет вам делать то, что вы хотите сделать (если не в точности с синтаксисом, который вам нужен): в lib.inc
define следующие дополнительные макросы:
#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x ## y
#define NAME_(name) CAT(NAME, name)
Затем вы можете использовать этот макрос NAME_()
, чтобы объединить расширение NAME
void NAME_(Init)() {
}