C/C++ 宏字符串连接

Posted

技术标签:

【中文标题】C/C++ 宏字符串连接【英文标题】:C/C++ macro string concatenation 【发布时间】:2011-07-12 11:53:24 【问题描述】:
#define STR1      "s"
#define STR2      "1"
#define STR3      STR1 ## STR2

是否可以将STR1STR2 连接到"s1"? 您可以通过将 args 传递给另一个宏函数来做到这一点。但是有直接的方法吗?

【问题讨论】:

不应该是#define STR3 STR1 ## STR2 它也不应该是,因为这将 STR3 定义为预处理令牌 STR1STR2。并且将 args 传递给另一个宏函数也无济于事,因为字符串文字不能粘贴在一起——“s”“1”不是有效的标记。 【参考方案1】:

如果它们都是字符串,你可以这样做:

#define STR3 STR1 STR2

然后扩展为:

#define STR3 "s" "1"

在 C 语言中,用空格分隔两个字符串,如 "s" "1" 中一样,完全等同于有一个字符串 "s1"

【讨论】:

从技术上讲,字符串连接是在语言级别完成的。 预处理器不做这样的事情。正是 C 语言本身将相邻的字符串文字视为单个字符串文字。 这不仅仅是一个技术问题——你不能连接L"a""b"来得到L"ab",但是你可以连接L"a"L"b"获取L"ab" 如果你尝试#include STR3 并且STR3 是一个有效的头文件,这将不起作用。有人知道怎么做吗? @Zythos - 您可能想发布一个单独的问题,详细说明您正在尝试做什么、您期望发生什么以及实际发生的事情。【参考方案2】:

对于字符串文字,您不需要这种解决方案,因为它们是在语言级别连接的,而且无论如何它都不会工作,因为“s”“1”不是有效的预处理器标记。

[编辑:为了回应下面错误的“仅供记录”评论,不幸收到了几个赞成票,我将重申上面的声明并观察程序片段

#define PPCAT_NX(A, B) A ## B
PPCAT_NX("s", "1")

从 gcc 的预处理阶段产生此错误消息:错误:粘贴“s”和“1”不提供有效的预处理令牌

]

但是,对于一般的令牌粘贴,试试这个:

/*
 * Concatenate preprocessor tokens A and B without expanding macro definitions
 * (however, if invoked from a macro, macro arguments are expanded).
 */
#define PPCAT_NX(A, B) A ## B

/*
 * Concatenate preprocessor tokens A and B after macro-expanding them.
 */
#define PPCAT(A, B) PPCAT_NX(A, B)

然后,例如,PPCAT_NX(s, 1)PPCAT(s, 1) 都生成标识符 s1,除非 s 被定义为宏,在这种情况下 PPCAT(s, 1) 生成 <macro value of s>1

继续主题是这些宏:

/*
 * Turn A into a string literal without expanding macro definitions
 * (however, if invoked from a macro, macro arguments are expanded).
 */
#define STRINGIZE_NX(A) #A

/*
 * Turn A into a string literal after macro-expanding it.
 */
#define STRINGIZE(A) STRINGIZE_NX(A)

那么,

#define T1 s
#define T2 1
STRINGIZE(PPCAT(T1, T2)) // produces "s1"

相比之下,

STRINGIZE(PPCAT_NX(T1, T2)) // produces "T1T2"
STRINGIZE_NX(PPCAT_NX(T1, T2)) // produces "PPCAT_NX(T1, T2)"

#define T1T2 visit the zoo
STRINGIZE(PPCAT_NX(T1, T2)) // produces "visit the zoo"
STRINGIZE_NX(PPCAT(T1, T2)) // produces "PPCAT(T1, T2)"

【讨论】:

仅作记录,"s""1" 在 C(和 C++)中有效。它们是两个标记(字符串文字),编译器会将自身连接起来并作为一个标记进行威胁。 你误解了我的评论和 C 语言。我说"s""1" isn't a valid token——没错;正如您所说,它是 两个 标记。但是将它们与 ## 一起使用会使它们成为 single 预处理标记,而不是两个标记,因此编译器不会进行连接,而是词法分析器会拒绝它们(语言需要诊断)。 @mr5 仔细阅读 cmets。作为宏参数传递的宏名称在传递之前不会扩展。但是,它们在宏的主体中进行了扩展。因此,如果 A 定义为 FRED,则 STRINGIZE_NX(A) 扩展为“A”,但 STRINGIZE(A) 扩展为 STRINGIZE_NX(FRED),后者扩展为“FRED”。 @bharath 结果字符串为“PPCAT(T1,T2)”——符合预期。 而不是预期的“s1”——根本不是预期的。 为什么我们需要额外的间接/嵌套? -- 阅读代码 cmets,以及我在上面的 6 个赞成票的评论。只有宏的主体被扩展;在宏体之外,括号之间的宏参数在传递给宏之前扩展。所以STRINGIZE_NX(whatever occurs here) 扩展为“这里发生的任何事情”,而不管发生的任何事情或这里发生的任何宏定义。 @bharath 当然它不会打印“Name A”——A 是参数名称,而不是宏的参数,即 ALEX。您声称if A is defined as FRED then STRINGIZE_NX(A) still expands to "FRED" - 这是错误的,与您的测试完全不同。您正在努力不理解或正确理解这一点,我不会进一步回复您。【参考方案3】:

提示:上面的STRINGIZE 宏很酷,但是如果你犯了一个错误并且它的参数不是宏——你的名字有错别字,或者忘记了#include 头文件——那么编译器会很高兴地将声称的宏名称放入字符串中而不会出错。

如果您希望 STRINGIZE 的参数始终是具有正常 C 值的宏,那么

#define STRINGIZE(A) ((A),STRINGIZE_NX(A))

将展开它一次并检查它的有效性,丢弃它,然后将它再次展开成一个字符串。

我花了一段时间才弄明白为什么 STRINGIZE(ENOENT) 最终会变成 "ENOENT" 而不是 "2"...我没有包括 errno.h

【讨论】:

重要的观察和 +1 以正确使用 , 运算符。 :) 字符串的内容应该是一个有效的 C 表达式并没有什么特别的原因。如果你想这样做,我建议给它一个不同的名字,比如 STRINGIZE_EXPR。 这个技巧可能是孤立的。但它会阻止编译器看到它将连接的字符串序列。 (导致像((1),"1") "." ((2),"2") 这样的序列,而不仅仅是“1”“。”“2”) 只是为了澄清自守的意思:使用原始的 STRINGIZE 定义,"The value of ENOENT is " STRINGIZE(ENOENT) 有效,而 "The value of ENOENT is" STRINGIZE_EXPR(X) 产生错误。

以上是关于C/C++ 宏字符串连接的主要内容,如果未能解决你的问题,请参考以下文章

1. 宏定义

C语言的宏定义,字符串连接

C/C++ 宏拼接和宏展开为字符串

如何定义宏以将连接的char字符串转换为C中的wchar_t字符串

C++/C 宏定义(define)中# ## 的含义 宏拼接

关于C语言宏定义输出