#define之外的C预处理器连接

时间:2016-03-10 19:27:49

标签: c c-preprocessor

我想知道为什么我们不能在define之外使用令牌连接。

当我同时想要这些时,会出现这种情况:

  • 库中的无冲突命名(或“泛型”)
  • 可调试;当为此使用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();
          ^

经验法则是“仅在定义中连续”。如果我没记错的话:原因在于预处理器阶段。 问题:为什么不起作用。阶段参数听起来像曾经是一个实现限制(而不是逻辑原因),然后进入标准。如果NAME##Init()工作正常,接受NAME()可能会有什么困难?

3 个答案:

答案 0 :(得分:79)

为什么这不是一个简单的问题。也许现在是时候向标准委员会询问为什么他们疯狂到标准化(现已删除)gets()功能呢?

有时,无论我们是否愿意,标准都只是脑死亡。第一个C不是今天的C.它不是"设计"成为今天的C,但是"长大了#34;进去。这导致了道路上的一些不一致和设计缺陷。在非指令行中允许##是完全有效的,但同样,C已经增长,而不是构建。 让我们不要再谈论同一模型带来C ++的后果......

无论如何,我们不是为了美化这些标准,所以有一种解决方法。首先,在lib.inc ...

#include <stdio.h>

#ifndef NAME
    #error Includer should first define 'NAME'!
#endif

// We need 'CAT_HELPER' because of the preprocessor's 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's 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;
}

答案 1 :(得分:37)

在“ANSI C Rationale”文档的3.8.3.3节中,解释了##运算符背后的推理。其中一个基本原则是:

  

作为##的操作数的形式参数(或普通操作数)在粘贴之前不会展开。

这意味着您将获得以下内容:

#define NAME foo

void NAME##init();   // yields "NAMEinit", not "fooinit"

这使得它在这种情况下无用,并解释了为什么必须使用两层宏来连接存储在宏中的内容。简单地改变运算符以始终首先扩展操作数将不是一个理想的解决方案,因为现在你不能(在这个例子中)也可以与显式字符串“NAME”连接,如果你想;它总是首先扩展到宏值。

答案 2 :(得分:9)

虽然大部分C语言在标准化之前已经发展和发展,但##被C89委员会发明,所以他们确实可以决定使用另一种方法。我不是一个通灵者所以我不能告诉为什么 C89标准委员会决定将令牌粘贴标准化它的确切方式,但ANSI C原理3.8.3.3规定&#34; [its设计]原则编纂了现有技术的基本特征,并与字符串化算子的规范一致。&#34;

但更改标准以便{mm}允许在宏观主体之外使用对您的情况没有太大用处:X ## YX不会被扩展在Y之前应用于宏体中,因此即使可以让##在宏体外部具有预期结果,也必须更改NAME ## Init的语义。如果它的语义没有改变,你仍然需要间接。获得间接的唯一方法是在宏体中使用它!

Why does an SSH remote command get fewer environment variables then when run manually?(如果不完全符合您想要的语法):在您的##中定义以下额外的宏:

lib.inc

然后您可以使用此#define CAT(x, y) CAT_(x, y) #define CAT_(x, y) x ## y #define NAME_(name) CAT(NAME, name) 宏来连接NAME_()的扩展

NAME