c preprocessor add a space when concatenate numbers with sign

Viewed 408

It seems that the preprocessor adds a space when concatenating tokens that are signed numbers. I tried this:

#define DECL_FL(IE) 1e##IE##f

float val[] = 
{
    DECL_FL(12),
    DECL_FL(-12),
    DECL_FL(+12),
};

the I run the preprocessor:

$ gcc test.c -E
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"

float val[] =
{
 1e12f,
 1e- 12f,
 1e+ 12f,
};

The first element in the array is correct. for the other two elements, a space is added between the number and the sign.
Why the preprocessor add a space? How can I avoid it?

2 Answers

The short version is that +12 isn't a preprocessor token, and neither is -12; rather, each of these are two preprocessor tokens (namely, + and - are punctuators; 12 separately is a pp-number).

We're essentially dealing with a pp-number, so here's the grammar rules for pp-number's:

pp-number: digit . digit pp-number digit pp-number identifier-nondigit pp-number e sign pp-number E sign pp-number p sign pp-number P sign pp-number . Note here that 1, 1e+, 1e+12, and 1e+12f are all pp-numbers, but +12 and -12 are not. This is what's biting you.

Per the preprocessor rules, a parameter in the replacement list is replaced by the argument token sequence; then, each ## is applied. The application removes ## and concatenates the preceding token to the following token. If that combination is not a valid preprocessor-token, the result is undefined. (For reference, a preprocessor-token can be a header name, identifier, pp-number, character-constant, string-literal, punctuator, or a non-whitespace character that's not one of those; that's it).

When applying 1e##IE##f with IE being +12, you're essentially doing <1e>##<+><12>##<f>, using angle braces to denote individual tokens. Both pastes, regardless of order (which is good, because the ## order is unspecified), will produce valid pp-numbers <1e+> and <12f>. But the result leaves you with <1e+><12f> rather than the desired <1e+12f>.

How can I avoid it?

Unfortunately you have to give up on passing +12 and -12 token sequences as an argument. You could accept +, 12 as two arguments, but you need to be careful when combining these in steps, since the order of ## operators is unspecified and +12 is not a valid preprocessor token (otherwise it might work, but would be allowed not to... and that situation is a potentially nightmarish time bomb):

#define PASTE(A,B) A##B
#define DECL_FL(IE) 1e##IE##f
#define DECL_FL_SGN(S,IE) PASTE(1e##S,IE##f)
float val[] = 
{
    DECL_FL(12),
    DECL_FL_SGN(+,12),
    DECL_FL_SGN(-,12),
};

...or you could simply use a sign specific macro; and strictly since 1e12f and 1e+12f are the same value (and nobody's going to see it anyway unless they just run the preprocessor), you can get away with only two macros:

#define DECL_FL(IE) 1e##IE##f
#define DECL_FL_E_NEG(IE) 1e-##IE##f
float val[] = 
{
    DECL_FL(12),
    DECL_FL_E_NEG(12),
    DECL_FL(12),
};

the ## is token pasting operator, this will not work if valid token is not formed after using it, as per C 1e+12f is not valid preprocessor token so it is trying insert space or it could lead to unexpected behavior, you can find it in following documentation.

http://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Related