不需要的C预处理器宏展开

6

我正在使用一个单元测试框架,该框架依赖于 REQUIRE 宏来执行断言。

简单来说,这个宏的功能如下:

#define REQUIRE( expr ) INTERNAL_REQUIRE( expr, "REQUIRE" )

这个定义与以下类似:

#define INTERNAL_REQUIRE( expr, macroName ) \
PerformAssertion( macroName, #expr, expr );

PerformAssertion的前两个参数是const char*类型。第二个参数(#expr)的原因是为了记录被断言的确切表达式。问题就在这里,预处理器在将表达式作为const char *传递之前会扩展表达式,因此它不是最初断言的相同表达式。

例如:

REQUIRE( foo != NULL );

会导致这个调用:
PerformAssertion( "REQUIRE", "foo != 0", foo != 0 );

如您所见,表达式已部分展开,例如日志中的表达式foo != NULL被展开为foo != 0。在构建断言消息文本之前,C预处理器扩展了NULL(这是一个定义为0的宏)。我是否有办法忽略或绕过消息文本的扩展?
编辑:以下是解决方案,供任何感兴趣的人参考:
#define REQUIRE( expr ) INTERNAL_REQUIRE( expr, #expr, "REQUIRE" )

#define INTERNAL_REQUIRE( expr, exprString, macroName ) \
PerformAssertion( macroName, exprString, expr );

2
只需使用INTERNAL_REQUIRE(expr,#expr,"REQUIRE")而不是两个参数形式吗? - kennytm
2个回答

5

尝试在调用内部require之前进行字符串化。你的问题是它在第二次扩展中传递给内部require,该扩展会扩展为NULL。如果您在require宏中先进行字符串化,它将不会扩展为NULL。


工作得非常完美,谢谢!您能否详细解释一下它是如何工作的? - Jeff

2
这就是发生的事情:由于应用了“字符串化”运算符#的宏位于第二级,操作序列的顺序如下:
- 预处理器识别REQUIRE(NULL)的参数,并根据C 6.10.3.1执行参数替换。此时,替换看起来像INTERNAL_REQUIRE( 0, "REQUIRE" ),因为NULL被扩展为0。 - 预处理器继续通过INTERNAL_REQUIRE扩展宏链;此时,使用NULL调用宏的事实丢失了:对于预处理器而言,传递给INTERNAL_REQUIRE的表达式是0
解决这个问题的关键在于标准中的这一段话:

除非前面带有#或##预处理标记,或后面跟着一个##预处理标记(参见下文),否则替换列表中的参数将在其中包含的所有宏展开后由相应的参数替换。

这意味着,如果您想捕获精确的表达式,您需要在宏扩展的第一级中完成。

谢谢,这满足了我的好奇心并解释了为什么Dani的解决方案有效。 - Jeff

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