Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unwanted C Preprocessor Macro Expansion

I'm using a unit test framework that relies on a REQUIRE macro for performing assertions.

Simplified, the macro works like this:

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

Which is defined similar to this:

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

PerformAssertion's first two parameters are of the type: const char*. The reason for the second parameter (#expr) is so the exact expression that was asserted can be logged. This is where the issue lies. The preprocessor expands the expression before it is passed as a const char *, so it's not the same expression that was originally asserted.

For instance:

REQUIRE( foo != NULL );

Would result in this call:

PerformAssertion( "REQUIRE", "foo != 0", foo != 0 );

As you can see, the expression is partially expanded, e.g. the expression foo != NULL appears in the log as foo != 0. The NULL (which is a macro defined to be 0) was expanded by the C preprocessor before building the assertions message text. Is there a way I can ignore or bypass the expansion for the message text?

EDIT: Here's the solution, for anyone curious:

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

#define INTERNAL_REQUIRE( expr, exprString, macroName ) \
PerformAssertion( macroName, exprString, expr );
like image 972
Jeff Avatar asked Jun 19 '12 17:06

Jeff


2 Answers

Try making the stringifying before the call to the internal require. Your problem is that it is passed to internal require in the second expansion which expands NULL. If you make the stringifying happen before that, e.g. In the require macro, it will not expand the NULL.

like image 138
Dani Avatar answered Oct 17 '22 09:10

Dani


Here is what's going on: since you macro where the "stringization" operator # is applied is second-level, the sequence of operations works as follows:

  • Preprocessor identifies the arguments of REQUIRE(NULL) and performs argument substitution as per C 6.10.3.1. At this point, the replacement looks like INTERNAL_REQUIRE( 0, "REQUIRE" ), because NULL is expanded as 0.
  • Preprocessor continues expanding the macro chain with INTERNAL_REQUIRE; at this point, the fact that the macro has been called with NULL is lost: as far as the preprocessor is concerned, the expression passed to INTERNAL_REQUIRE is 0.

A key to solving this problem is in this paragraph from the standard:

A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded.

This means that if you would like to capture the exact expression, you need to do it in the very first level of the macro expansion.

like image 30
Sergey Kalinichenko Avatar answered Oct 17 '22 08:10

Sergey Kalinichenko