在 #define 中,## 是什么意思?

42

这行代码的意思是什么?特别是,## 是什么意思?

#define ANALYZE(variable, flag)     ((Something.##variable) & (flag))

编辑:

还是有点困惑。如果没有 ##,结果会是什么样子?


请参见:https://dev59.com/eHI-5IYBdhLWcg3w9tdw - sharkin
6个回答

24

有点困惑。没有 ## 的话结果会是什么?

通常情况下,您不会注意到任何差异。但是确实有一个区别。假设Something是以下类型:

struct X { int x; };
X Something;

再看一下:

int X::*p = &X::x;
ANALYZE(x, flag)
ANALYZE(*p, flag)

没有 token 连接操作符 ##,它会展开为:

#define ANALYZE(variable, flag)     ((Something.variable) & (flag))

((Something. x) & (flag))
((Something. *p) & (flag)) // . and * are not concatenated to one token. syntax error!

使用令牌拼接后,它会扩展为:

#define ANALYZE(variable, flag)     ((Something.##variable) & (flag))

((Something.x) & (flag))
((Something.*p) & (flag)) // .* is a newly generated token, now it works!

记住,预处理器操作的是预处理器标记,而不是文本。所以如果你想连接两个标记,你必须明确地说明。


19

3
实际上这被称为标记串联。我认为IBM AIX C/C++编译器的文档并不是最好的参考资料! - David Heffernan
@David:我已经将这个添加到我的答案中。谢谢 :-) - Nawaz
@David:我并没有说IBM AIX C/C++编译器是最好的参考资料。最好的参考资料当然就是标准本身。但我们仍然会提供其他网站的链接,包括在stackoverflow上的主题。 - Nawaz
标准对于这样的问题来说是一个毫无帮助的参考。我不确定有什么好的参考资料。也许那个IBM AIX参考文档中的内容不错,但很遗憾他们没能正确命名。 - David Heffernan

11

其中一个非常重要的部分是,这个标记串联遵循一些非常特殊的规则:

例如 IBM 文档:

  • 在展开宏之前,串联会首先发生。
  • 如果串联的结果是一个有效的宏名称,即使出现在通常不可用的上下文中,它也可以用于进一步的替换。
  • 如果宏定义的替换列表中出现一个以上的 ## 运算符和/或 # 运算符,则运算符的执行顺序未定义。

示例也非常自我解释

#define ArgArg(x, y)          x##y
#define ArgText(x)            x##TEXT
#define TextArg(x)            TEXT##x
#define TextText              TEXT##text
#define Jitter                1
#define bug                   2
#define Jitterbug             3

输出结果为:

ArgArg(lady, bug)   "ladybug"
ArgText(con)    "conTEXT"
TextArg(book)   "TEXTbook"
TextText    "TEXTtext"
ArgArg(Jitter, bug)     3

这段内容的来源是IBM文档。在其他编译器中可能会有所不同。

针对您的代码行:

它将变量属性连接到“Something.”并寻址逻辑与运算后的变量,如果Something.variable设置了标志,则返回结果。

以下是我的上一条评论和您的问题的示例(可使用g++编译):

// this one fails with a compiler error
// #define ANALYZE1(variable, flag)     ((Something.##variable) & (flag))
// this one will address Something.a (struct)
#define ANALYZE2(variable, flag)     ((Something.variable) & (flag))
// this one will be Somethinga (global)
#define ANALYZE3(variable, flag)     ((Something##variable) & (flag))
#include <iostream>
using namespace std;

struct something{
int a;
};

int Somethinga = 0;

int main()
{
something Something;
Something.a = 1;

if (ANALYZE2(a,1))
    cout << "Something.a is 1" << endl;
if (!ANALYZE3(a,1))
    cout << "Somethinga is 0" << endl;
        return 1;
};

没有##的情况下,结果会是什么? - Dante May Code
我的测试结果是这样的。GCC 抱怨 a.##ba##b 右侧求值为全局变量。a.b 也右侧求值为结构体实例 a 内的变量。 - fyr
我添加了一个示例作为补充。 - fyr

4
这不是对你问题的回答,而是一篇带有一些提示的CW帖子,以帮助你自己探索预处理器。

预处理步骤实际上是在任何实际代码被编译之前执行的。换句话说,当编译器开始构建你的代码时,没有留下任何#define语句或类似的东西。

理解预处理器对你的代码所做的事情的好方法是获取预处理输出并查看它。

这是在Windows中如何做到:

创建一个名为test.cpp的简单文件并将其放置在文件夹中,比如c:\temp。 我的看起来像这样:

#define dog_suffix( variable_name ) variable_name##dog

int main()
{
  int dog_suffix( my_int ) = 0;
  char dog_suffix( my_char ) = 'a';

  return 0;
}

虽然不是很有用,但很简单。打开Visual Studio命令提示符,导航到文件夹并运行以下命令行:

c:\temp>cl test.cpp /P

所以,你运行的是编译器(cl.exe),并使用 /P 选项告诉编译器将预处理输出保存到文件中。

现在,在 test.cpp 的旁边文件夹中,你会找到 test.i 文件,对我而言,它看起来像这样:

#line 1 "test.cpp"


int main()
{
  int my_intdog = 0;
  char my_chardog = 'a';

  return 0;
}

正如您所看到的,没有任何#define了,只剩下它所展开的代码。


3
让我们考虑一个不同的例子:
考虑
#define MYMACRO(x,y) x##y

没有##,预处理器就不能将xy作为不同的标记来看待,对吗?
在你的例子中,
#define ANALYZE(variable, flag)     ((Something.##variable) & (flag))

## 并不需要,因为您没有创建新的标识符。实际上,编译器会发出“错误:拼接'.'和'variable'不会产生有效的预处理令牌”。


3

根据维基百科

标记串联,也称为标记粘合,是C宏预处理器中最微妙且容易被滥用的功能之一。使用##预处理器运算符可以将两个参数“粘合”在一起;这允许在预处理代码中连接两个标记。这可以用于构建复杂的宏,其作用类似于C ++模板的原始版本。

请查看标记串联


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接