我有这段代码(实际上是垃圾收集 Forth 系统解释器的一部分):
#define PRIMITIVE(name) \
do \
{ \
VocabEntry* entry = (VocabEntry*)gc_alloc(sizeof(VocabEntry)); \
entry->code = name; \
entry->name = cstr_to_pstr(#name); \
entry->prev = latest_vocab_entry; \
latest_vocab_entry = entry; \
} \
while (false)
PRIMITIVE(dup);
PRIMITIVE(drop);
PRIMITIVE(swap);
// and a lot more
但是有一个问题:在队列中
entry->name = cstr_to_pstr(#name);
name
字段替换为 dup
、drop
、swap
等。我希望字段名称不被替换。
那么,除了简单地重命名宏参数之外,还有什么方法可以解决这个问题吗?
对于答案,请解释一般情况下是否有一种方法可以抑制宏体中宏参数名称的替换。不要回答“就这样做吧”(请)。
Jim Balter在评论中提供了这个解决方案:将宏替换列表中的
name
更改为na##me
。连接运算符导致其被 name
替换,但这是在扫描宏参数名称之后发生的,因此在该扫描中不会被替换。代码是:
#define PRIMITIVE(name) \
do \
{ \
VocabEntry* entry = (VocabEntry*)gc_alloc(sizeof(VocabEntry)); \
entry->code = name; \
entry->na##me = cstr_to_pstr(#name); \
entry->prev = latest_vocab_entry; \
latest_vocab_entry = entry; \
} \
while (false)
我原来的答案有点麻烦:
您可以定义一个不同的宏来扩展为
name
,如下所示:
#define Name name
并更改
name
宏中的 PRIMITIVE
字段以使用新宏,如下所示:
#define PRIMITIVE(name) \
do \
{ \
VocabEntry* entry = (VocabEntry*)gc_alloc(sizeof(VocabEntry)); \
entry->code = name; \
entry->Name = cstr_to_pstr(#name); \
entry->prev = latest_vocab_entry; \
latest_vocab_entry = entry; \
} \
while (false)
除了在宏体中使用与参数名称不同的内容或更改参数名称之外,在 C 语言中没有其他方法可以做到这一点。根据 C 2011 (N1570) 6.10.3.1 1,当识别出类似函数的宏时,参数名称将立即被替换,除非存在
#
或 ##
,并且没有其他例外:
在识别出调用类似函数的宏的参数后,将进行参数替换。替换列表中的参数,除非前面有 # 或 ## 预处理标记或后面有 ## 预处理标记(见下文),否则在其中包含的所有宏都已展开后将被相应的参数替换。
#
标记将参数名称更改为字符串,在这种情况下没有用。 ##
标记扩展参数名称并将其与相邻标记粘贴在一起,这在这种情况下也是没有用的。
不,没有。
要了解原因,您需要考虑宏扩展实际发生的方式。扩展类似函数的宏需要三个主要步骤:
#
或 ##
运算符(在示例中不相关,因为它们是单个标记)标准第 6.10.3 节(C11 和 C99)对此进行了概述。
这样做的结果是,不可能编写某种可以采用
name
并滥用 '##' 或类似内容的抑制规则的宏,因为 PRIMITIVE
主体中的替换步骤在主体内的任何宏被允许轮到被识别之前,必须运行完全。您无法对替换列表中的标记进行标记以进行抑制,因为您可以在其上放置的任何标记只会在替换步骤已经运行后才会进行检查。由于标准中指定了顺序,因此您发现的任何允许您以这种方式标记令牌的漏洞都是编译器错误。
如果您真的决定不重命名宏参数,我最好的建议是将
na
和 me
作为单独的参数传递给串联宏;标记 name
仅在替换完成后才会形成,并且不再检查列表中的参数名称。
编辑希望我打字更快。
不,没有办法禁止在宏体内替换与该宏的声明参数相同的标记。除了跳入预处理器代码之外,每种可能的解决方案都需要您重命名某些内容,无论是参数名称还是字段名称(可能只是为了该宏的目的,正如埃里克的答案所做的那样)。