【问题标题】:C preprocessor concatenation outside of #define#define 之外的 C 预处理器连接
【发布时间】:2016-06-25 19:53:47
【问题描述】:

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

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

  • 库中的无冲突命名(或“泛型”)
  • 可调试性;当为此使用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() 工作正常,接受NAME##Init() 会有什么困难?

【问题讨论】:

  • 嗯,很天真的事情是预处理器指令总是开始#.. 我猜预处理器开发人员只是懒得解析其他东西。
  • 如果你能说服 C 委员会接受它,它可能会起作用。但是NAME##Init#define NAME_(x) NAME##x 加上NAME_(Init) 的优势可能并不值得。
  • @EugeneSh.: 但是当使用宏时,预处理器必须读取所有代码来识别和替换。该提案只会将其扩展到相邻的##
  • @Peter 这经常用于“通用”C 库中。查看任何通用库,您将看到该模式。用户可能会编写 DEF(Foo, int) 和 DEF(Bar, void*) 并获得两个处理不同数据类型的不同函数。
  • 我认为最接近答案的可能是 C 标准基本原理中关于## 的这一行:“这些原则编纂了现有技术的基本特征,并且与字符串化运算符的规范一致。 "委员会试图不向尚未广泛使用的语言添加新功能;他们有时会更改语法以减少出错的可能性。

标签: c c-preprocessor


【解决方案1】:

为什么这不是一个简单的问题。也许是时候问问标准委员会了,为什么他们会疯狂地标准化(现已删除)gets() 函数?

有时,无论我们是否愿意,标准都只是脑残。第一个 C 不是今天的 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'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;
}

【讨论】:

    【解决方案2】:

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

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

    这意味着您将获得以下信息:

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

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

    【讨论】:

      【解决方案3】:

      虽然大部分 C 语言在其标准化之前已经发展和发展,但 ## 是由 C89 委员会发明的,因此他们确实也可以决定使用另一种方法。我不是通灵者,所以我无法说明为什么 C89 标准委员会决定将令牌粘贴标准化,但 ANSI C Rationale 3.8.3.3 指出 "[其设计] 原则整理了现有技术的本质特征,并与字符串化操作符的规范一致。”

      但是更改标准以使 X ## Y 允许在宏主体之外在您的情况下也没有多大用处:XY## 应用于宏之前不会被扩展体,所以即使有可能让NAME ## Init 在宏体之外获得预期的结果,## 的语义也必须改变。如果它的语义没有改变,你仍然需要间接。获得这种间接性的唯一方法就是在宏体内使用它!

      C preprocessor already allows you to do what you want to do(如果不完全符合您想要的语法):在您的 lib.inc 中定义以下额外宏:

      #define CAT(x, y) CAT_(x, y)
      #define CAT_(x, y) x ## y
      #define NAME_(name) CAT(NAME, name)
      

      然后你可以使用这个NAME_()宏来连接NAME的扩展

      void NAME_(Init)() {
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-22
        • 1970-01-01
        • 2012-03-28
        • 1970-01-01
        • 1970-01-01
        • 2015-09-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多