使用##和__LINE__创建C宏(定位宏的标记连接)。

128

我想创建一个基于行号命名的函数的 C 宏。 我认为可以像这样实现(实际函数将在大括号内包含语句):

#define UNIQUE static void Unique_##__LINE__(void) {}
我希望这将扩展为类似于以下内容:
static void Unique_23(void) {}

那样行不通。使用标记连接时,定位宏被视为字面量,最终展开为:

static void Unique___LINE__(void) {}

这个能做到吗?


我认为你可以使用间接宏扩展来使其工作。 - Ben Stiglitz
5
可能是重复的问题:如何使用C预处理器两次连接并展开宏,就像“arg##_##MACRO”一样? 对于除了__LINE__之外的任何宏都适用(尽管后者是常见用例)。 - Ciro Santilli OurBigBook.com
3个回答

210

问题在于当你有一个宏替换时,如果没有应用字符串化运算符#或标记粘贴运算符##,预处理器只会递归展开宏。因此,你必须使用一些额外的间接层,可以使用带有递归展开参数的标记粘贴运算符:

#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#define UNIQUE static void TOKENPASTE2(Unique_, __LINE__)(void) {}

然后,__LINE__UNIQUE展开时被扩展为该行的行号(因为它与###没有关联),然后在TOKENPASTE展开过程中进行记号粘贴。

还应该注意到还有__COUNTER__宏,每次被评估时都会扩展为一个新的整数,以防需要在同一行上具有多个UNIQUE宏的实例化。注意:__COUNTER__受到MS Visual Studio、GCC(自V4.3起)和Clang支持,但不是标准的C语言。


3
抱歉,GNU cpp 不支持这种方式。TOKENPASTE 使用 LINE 作为文字。TOKENPASTE(Unique_, LINE) 将扩展为 Unique___LINE__。 - DD.
4
@DD: 哦,现在已经修复了。需要两层间接引用,而不是一层。 - Adam Rosenfield
2
对于任何试图使用__COUNTER__的人来说,需要额外的信息。根据http://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx,它是一个特定于Microsoft的宏。 - Elva
@Yourdoom:GCC也支持__COUNTER__(自V4.3起)。我编辑了答案。 - sleske
5
为什么需要两个间接级别的解释?我尝试了只用一个间接级别,没有使用#和##,但它在VS2017上无法扩展。显然GCC也是如此。但是如果增加第二个间接级别,它就会扩展。这是神奇的吗? - Gabe Halsmer
显示剩余5条评论

2

如何使用宏自动生成带有行号的唯一变量名

这是一个通用的答案,不涉及到 OP 问题的具体细节,因为已经有足够的答案了。

我主要从 @Jarod42 这里 学到了这个方法,也从 @Adam.Rosenfield 这里 学到了一些。

#define CONCAT_(prefix, suffix) prefix##suffix
/// Concatenate `prefix, suffix` into `prefixsuffix`
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
/// Make a unique variable name containing the line number at the end of the
/// name. Ex: `uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0;` would
/// produce `uint64_t counter_7 = 0` if the call is on line 7!
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)

示例程序:

macro_make_unique_variable_name_with_line_number.c:

#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`

#define CONCAT_(prefix, suffix) prefix##suffix
/// Concatenate `prefix, suffix` into `prefixsuffix`
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
/// Make a unique variable name containing the line number at the end of the
/// name. Ex: `uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0;` would
/// produce `uint64_t counter_7 = 0` if the call is on line 7!
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)


// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("Autogenerate unique variable names containing the line number "
           "in them.\n\n");

    uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_54 = 0;
    uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_55 = 0;
    uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_56 = 0;

    // Uncomment this to suppress the errors.
    // (void)counter_54;
    // (void)counter_55;
    // (void)counter_56;

    return 0;
}

样例输出:

请注意,故意产生的构建错误揭示了自动生成的变量名为counter_56counter_55counter_54,如下所示!:

macro_make_unique_variable_name_with_line_number.c:56:40: error: unused variable ‘counter_56’ [-Werror=unused-variable]
macro_make_unique_variable_name_with_line_number.c:55:40: error: unused variable ‘counter_55’ [-Werror=unused-variable]
macro_make_unique_variable_name_with_line_number.c:54:40: error: unused variable ‘counter_54’ [-Werror=unused-variable]

完整输出:

eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=gnu17 macro_make_unique_variable_name_with_line_number.c -o bin/a -lm && bin/a
macro_make_unique_variable_name_with_line_number.c: In function ‘main’:
macro_make_unique_variable_name_with_line_number.c:56:40: error: unused variable ‘counter_56’ [-Werror=unused-variable]
   56 |     uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_56 = 0;
      |                                        ^~~~~~~
macro_make_unique_variable_name_with_line_number.c:39:33: note: in definition of macro ‘CONCAT_’
   39 | #define CONCAT_(prefix, suffix) prefix##suffix
      |                                 ^~~~~~
macro_make_unique_variable_name_with_line_number.c:45:43: note: in expansion of macro ‘CONCAT’
   45 | #define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)
      |                                           ^~~~~~
macro_make_unique_variable_name_with_line_number.c:56:14: note: in expansion of macro ‘MAKE_UNIQUE_VARIABLE_NAME’
   56 |     uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_56 = 0;
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~
macro_make_unique_variable_name_with_line_number.c:55:40: error: unused variable ‘counter_55’ [-Werror=unused-variable]
   55 |     uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_55 = 0;
      |                                        ^~~~~~~
macro_make_unique_variable_name_with_line_number.c:39:33: note: in definition of macro ‘CONCAT_’
   39 | #define CONCAT_(prefix, suffix) prefix##suffix
      |                                 ^~~~~~
macro_make_unique_variable_name_with_line_number.c:45:43: note: in expansion of macro ‘CONCAT’
   45 | #define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)
      |                                           ^~~~~~
macro_make_unique_variable_name_with_line_number.c:55:14: note: in expansion of macro ‘MAKE_UNIQUE_VARIABLE_NAME’
   55 |     uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_55 = 0;
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~
macro_make_unique_variable_name_with_line_number.c:54:40: error: unused variable ‘counter_54’ [-Werror=unused-variable]
   54 |     uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_54 = 0;
      |                                        ^~~~~~~
macro_make_unique_variable_name_with_line_number.c:39:33: note: in definition of macro ‘CONCAT_’
   39 | #define CONCAT_(prefix, suffix) prefix##suffix
      |                                 ^~~~~~
macro_make_unique_variable_name_with_line_number.c:45:43: note: in expansion of macro ‘CONCAT’
   45 | #define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)
      |                                           ^~~~~~
macro_make_unique_variable_name_with_line_number.c:54:14: note: in expansion of macro ‘MAKE_UNIQUE_VARIABLE_NAME’
   54 |     uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0; // `uint64_t counter_54 = 0;
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors

-3

GCC不需要“包装”(或实现),除非结果需要“字符串化”。GCC具有功能,但所有功能都可以使用普通的C版本1完成(有人认为伯克利4.3 C更快,值得学习如何使用)。

** Clang(llvm)对于宏展开无法正确处理空格-它添加了空格(这肯定会破坏结果作为C标识符进行进一步预处理),clang只是不做#或*宏展开,因为C预处理器预期已经做了几十年。最主要的例子是编译X11,宏“Concat3”已经损坏,它的结果现在被错误地命名为C标识符,当然无法构建。我开始认为构建失败是他们的职业。

我认为答案在于“打破标准的新C是不好的C”,这些黑客总是选择(破坏命名空间),他们没有理由改变默认设置,但并没有真正“改进C”(除了他们自己说的话:我认为这是一个构想,用来解释为什么他们能够逃脱所有违规行为,而没有人让他们负责)。


早期的C预处理器不支持UNIq_()__并不是问题,因为它们支持#pragma,允许在代码中标记“编译器品牌hackery”,而且同样可以很好地工作,而不会影响标准:就像更改默认值是无用的破坏一样,使用相同名称更改函数的操作(命名空间污染)也是...在我看来是恶意软件。


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