【问题标题】:Valid preprocessor tokens in macro concatenation宏连接中的有效预处理器标记
【发布时间】:2017-06-01 04:00:30
【问题描述】:

我尝试使用串联预处理器运算符## 来理解 c 中的宏,但我意识到我在使用标记时遇到了问题。我认为这很容易,但实际上并非如此。

所以连接是为了连接两个令牌以创建一个新令牌。 例如:连接 ()int*

我试过了

#define foo(x,y) x ## y
foo(x,y)

每当我给它一些论点时,我总是会出错,说pasting both argument does not give a valid preprocessor token.

例如为什么连接foo(1,aa) 会导致1aa它是哪种类型的令牌?为什么它是有效的)但是foo(int,*) 我得到了一个错误。

有没有办法知道哪些令牌是有效的,或者是否有可能有一些好的链接来理解如何在我的脑海中澄清它。 (我已经在 google 和 SO 中搜索过)

我错过了什么?

我将不胜感激。

【问题讨论】:

  • 请求链接是题外话。如果您确实展示了导致该错误传递的内容,您可能会获得帮助。
  • 我认为1aa 不是有效的预处理令牌
  • 它不属于任何 6 种令牌类型,但 gcc 什么也没说,甚至没有警告?
  • @mm: 1aa 是一个preprocessing number;它是预处理器的有效令牌。 (一个 pp-number 可以在数字之后有任何标识符字符序列,这从标准的词汇语法中可以看出。)

标签: c gcc macros c-preprocessor


【解决方案1】:

预处理器标记连接用于生成新标记,但它不能将任意语言结构粘贴在一起(例如,gcc documentation):

但是,不能一起形成有效令牌的两个令牌不能 粘贴在一起。例如,您不能将 x 与 + in 连接起来 任意顺序。

因此,尝试使用一个宏来将指针变成像

这样的类型
#define MAKEPTR(NAME)  NAME ## *
MAKEPTR(int) myIntPtr;

无效,因为int* 是两个令牌,而不是一个。

然而,上述链接的示例显示了新令牌的生成:

 #define COMMAND(NAME)  { #NAME, NAME ## _command }

 struct command commands[] =
 {
   COMMAND (quit),
   COMMAND (help),
   ...
 };

产量:

 struct command commands[] =
 {
   { "quit", quit_command },
   { "help", help_command },
   ...
 };

令牌quit_command之前不存在,而是通过令牌串联生成的。

注意宏的形式

#define MAKEPTR(TYPE)  TYPE*
MAKEPTR(int) myIntPtr;

是有效的,并且实际上从TYPE 中生成了一个指针类型,例如int* 来自int

【讨论】:

  • 所以连接需要两个令牌来创建一个新的现有令牌对吗?
  • 它生成的 foo(1,foo) 怎么样 1foo 是一个标识符吗?并再次感谢。如果可能的话,请扩大论点?它发生与否?
  • 例如,1foo 是一个标记,但不是变量声明的有效标识符。所以即使令牌连接产生一个有效的令牌,这也不能保证这个令牌可以在任何上下文中使用。
  • 关于参数扩展:Docu 说:“与字符串化一样,实际参数首先不是宏扩展。”
  • 1foo 既不能是标识符也不能是关键字,所以预处理器如何知道它是否是 有效 令牌。 有效令牌的定义是什么? (感谢您的扩展,我也在 gnu 编译器文档中找到了相同的内容)
【解决方案2】:

预处理记号由C语言语法定义,见现行标准6.4节:

preprocessing-token:
                   header-name
                   identifier
                   pp-number
                   character-constant
                   string-literal
                   punctuator
                   each non-white-space character that cannot be one of the above

每个术语的含义都在语法的其他地方定义。大多数是不言自明的; identifier 表示任何有效变量名(或者如果它不是关键字),pp-number 包括整数和浮点常量。

在标准 C 中,粘贴两个预处理标记的结果必须是另一个有效的预处理标记。从历史上看,一些预处理器允许其他粘贴(相当于不粘贴!)但是当人们使用不同的编译器编译他们的代码时,这会导致混乱。

【讨论】:

  • 那么1aaa呢?
  • 不推荐使用##
  • 我不知道为什么 foo(-,1) 会失败,因为 -1 会是一个整数。
  • @StephanLechner -1 是两个标记,-(一元否定运算符)和1。没有负整数常量
  • #define concat3(a,b,c) a##b##c 怎么样...a##b 必须是一个有效的令牌还是我们可以在所有符合要求的实现上成功粘贴concat3(.,.,.)
【解决方案3】:

由于这似乎是一个混淆点,字符串1aa 是一个有效的预处理器标记;它是pp-number 的一个实例,其定义为(当前 C 标准的第 6.4.8 节):

     pp-number:
            digit
            . digit
            pp-number       digit
            pp-number       identifier-nondigit
            pp-number       e sign
            pp-number       E sign
            pp-number       p sign
            pp-number       P sign
            pp-number       .

换句话说,pp-number 以数字开头或 . 后跟一个数字,之后它可以包含任何数字序列,“identifier-nondigits”(即,字母、下划线和其他可以作为标识符一部分的内容)或字母 ep(大写或小写)后跟加号或减号.

这意味着,例如,0x1e+2 是有效的 pp-number,而 0x1f+1 不是(它是三个令牌)。在一个有效的程序中,每个在预处理阶段幸存下来的pp-number 必须满足一些数字常量表示的语法,这意味着包含文本0x1e+2 的程序将被视为无效。道德,如果有的话,就是你应该慷慨地使用空格;它没有成本。

pp-number 的目的是在 C 的某个未来版本中包含最终可能是数字的所有内容。(请记住,数字后面可以跟表示类型和符号的字母后缀,例如 27LU)。

但是,int* 不是有效的预处理器令牌。它是两个标记(-3 也是如此),因此不能使用标记连接运算符形成。

令牌粘贴规则的另一个奇怪后果是无法通过令牌连接生成有效令牌...,因为.. 不是有效令牌。 (a##b##c 必须按某种顺序计算,因此即使所有三个预处理器宏都扩展为 .,也必须尝试创建标记 ..,这在大多数编译器中都会失败,尽管我相信 Visual Studio 会接受它。)

最后,注释符号/*//不是标记;在将程序文本分隔成标记之前,cmets 被替换为空格。因此,您也不能生成带有标记粘贴的注释(至少,不是在兼容的编译器中)。

【讨论】:

  • pp-number 的宽松语法的一个有趣结果是 1_000 一个有效的 pp-number,这使得 _ 成为可选千位的理想候选者分隔符,同时保持与许多现有工具的兼容性。很遗憾看到 C++ 替代品 1'000 进入 C 标准,因为这个有问题的替代品几乎破坏了所有现有工具。
  • @chqrlie:当然。 C++的设计从来都不是对工具友好的;例如,就好像他们决定挫败任何语法高亮的尝试。请注意,预处理器已经使精确的工具变得困难。使用1'000 作为分隔符肯定是另一个烦恼(虽然它在哥伦比亚和其他地方很常见,所以有一些先例)。
【解决方案4】:

首先需要了解什么是 C 中的标记
标记是程序中最小的元素,对编译器有意义。

这里是列表: 代币可以分类如下:

Keywords
Identifiers
Constants
Strings
Special Symbols
Operators

让我们来看看下面的程序

#include <stdio.h>
#define concat(front, back) front ## back
int main() {
    printf("%i\n", concat(123, 456));
}

输出: 123456

上面的程序作为 123456 是 int 类型和一个常量值

让我们修改程序

 #include <stdio.h>
    #define concat(front, back) front ## back
    int main() {
        printf("%s\n", concat(aa, 1));
    }

以上会产生如下错误:

error: ‘aa1’ undeclared (first use in this function)
   47 |     concat(aa, 1)
      |            ^~

aa1 是一个有效的预处理器标记,也是一个有效的标识符,但未定义的标识符正是编译器抱怨“aa1”未声明的原因

要修复错误,如果我只定义 aa1 如下所示

 char aa1[] = "hello World"
  or 
 #define aa1 "Hello World"

然后它就可以正常工作了。

#include <stdio.h>
#define aa1 "Hello World"
#define concat(front, back) front ## back
int main() {
    printf("%s\n", concat(aa, 1));
}

输出:Hello World

现在试试concat(aa1, aa1),它仍然会生成有效的预处理器令牌和有效的标识符,但aa1aa1 没有定义

尝试concat(, aa1) // 假设aa1 已定义

希望以上解释对你有所帮助。

【讨论】:

  • 您可能想要扩展一点并说aa1 是一个有效的预处理器令牌,并且是一个有效的标识符,但在第二个程序中没有定义。您还应该解释 1aa 是一个有效的预处理器编号,但不是一个有效的 C 令牌。假设您正确定义了a1aa,第三个使用concat(a,1aa) 产生a1aa 的示例将说明这种奇怪。
  • @chqrlie 更新我的答案请看一下谢谢您的建议。
  • 您的修改解决了我评论的第一部分...
猜你喜欢
  • 1970-01-01
  • 2012-04-25
  • 2010-12-18
  • 2014-04-17
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多