How to concatenate twice with the C preprocessor and expand a macro as in “arg ## _ ## MACRO”?

前端 未结 3 1652
轮回少年
轮回少年 2020-11-21 10:03

I am trying to write a program where the names of some functions are dependent on the value of a certain macro variable with a macro like this:

#define VARIA         


        
3条回答
  •  臣服心动
    2020-11-21 10:56

    Standard C Preprocessor

    $ cat xx.c
    #define VARIABLE 3
    #define PASTER(x,y) x ## _ ## y
    #define EVALUATOR(x,y)  PASTER(x,y)
    #define NAME(fun) EVALUATOR(fun, VARIABLE)
    
    extern void NAME(mine)(char *x);
    $ gcc -E xx.c
    # 1 "xx.c"
    # 1 ""
    # 1 ""
    # 1 "xx.c"
    
    
    
    
    
    extern void mine_3(char *x);
    $
    

    Two levels of indirection

    In a comment to another answer, Cade Roux asked why this needs two levels of indirection. The flippant answer is because that's how the standard requires it to work; you tend to find you need the equivalent trick with the stringizing operator too.

    Section 6.10.3 of the C99 standard covers 'macro replacement', and 6.10.3.1 covers 'argument substitution'.

    After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. 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. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

    In the invocation NAME(mine), the argument is 'mine'; it is fully expanded to 'mine'; it is then substituted into the replacement string:

    EVALUATOR(mine, VARIABLE)
    

    Now the macro EVALUATOR is discovered, and the arguments are isolated as 'mine' and 'VARIABLE'; the latter is then fully expanded to '3', and substituted into the replacement string:

    PASTER(mine, 3)
    

    The operation of this is covered by other rules (6.10.3.3 'The ## operator'):

    If, in the replacement list of a function-like macro, a parameter is immediately preceded or followed by a ## preprocessing token, the parameter is replaced by the corresponding argument’s preprocessing token sequence; [...]

    For both object-like and function-like macro invocations, before the replacement list is reexamined for more macro names to replace, each instance of a ## preprocessing token in the replacement list (not from an argument) is deleted and the preceding preprocessing token is concatenated with the following preprocessing token.

    So, the replacement list contains x followed by ## and also ## followed by y; so we have:

    mine ## _ ## 3
    

    and eliminating the ## tokens and concatenating the tokens on either side combines 'mine' with '_' and '3' to yield:

    mine_3
    

    This is the desired result.


    If we look at the original question, the code was (adapted to use 'mine' instead of 'some_function'):

    #define VARIABLE 3
    #define NAME(fun) fun ## _ ## VARIABLE
    
    NAME(mine)
    

    The argument to NAME is clearly 'mine' and that is fully expanded.
    Following the rules of 6.10.3.3, we find:

    mine ## _ ## VARIABLE
    

    which, when the ## operators are eliminated, maps to:

    mine_VARIABLE
    

    exactly as reported in the question.


    Traditional C Preprocessor

    Robert Rüger asks:

    Is there any way do to this with the traditional C preprocessor which does not have the token pasting operator ##?

    Maybe, and maybe not — it depends on the preprocessor. One of the advantages of the standard preprocessor is that it has this facility which works reliably, whereas there were different implementations for pre-standard preprocessors. One requirement is that when the preprocessor replaces a comment, it does not generate a space as the ANSI preprocessor is required to do. The GCC (6.3.0) C Preprocessor meets this requirement; the Clang preprocessor from XCode 8.2.1 does not.

    When it works, this does the job (x-paste.c):

    #define VARIABLE 3
    #define PASTE2(x,y) x/**/y
    #define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
    #define NAME(fun) EVALUATOR(fun,VARIABLE)
    
    extern void NAME(mine)(char *x);
    

    Note that there isn't a space between fun, and VARIABLE — that is important because if present, it is copied to the output, and you end up with mine_ 3 as the name, which is not syntactically valid, of course. (Now, please can I have my hair back?)

    With GCC 6.3.0 (running cpp -traditional x-paste.c), I get:

    # 1 "x-paste.c"
    # 1 ""
    # 1 ""
    # 1 "x-paste.c"
    
    
    
    
    
    extern void mine_3(char *x);
    

    With Clang from XCode 8.2.1, I get:

    # 1 "x-paste.c"
    # 1 "" 1
    # 1 "" 3
    # 329 "" 3
    # 1 "" 1
    # 1 "" 2
    # 1 "x-paste.c" 2
    
    
    
    
    
    extern void mine _ 3(char *x);
    

    Those spaces spoil everything. I note that both preprocessors are correct; different pre-standard preprocessors exhibited both behaviours, which made token pasting an extremely annoying and unreliable process when trying to port code. The standard with the ## notation radically simplifies that.

    There might be other ways to do this. However, this does not work:

    #define VARIABLE 3
    #define PASTER(x,y) x/**/_/**/y
    #define EVALUATOR(x,y) PASTER(x,y)
    #define NAME(fun) EVALUATOR(fun,VARIABLE)
    
    extern void NAME(mine)(char *x);
    

    GCC generates:

    # 1 "x-paste.c"
    # 1 ""
    # 1 ""
    # 1 "x-paste.c"
    
    
    
    
    
    extern void mine_VARIABLE(char *x);
    

    Close, but no dice. YMMV, of course, depending on the pre-standard preprocessor that you're using. Frankly, if you're stuck with a preprocessor that is not cooperating, it would probably be simpler to arrange to use a standard C preprocessor in place of the pre-standard one (there is usually a way to configure the compiler appropriately) than to spend much time trying to work out a way to do the job.

提交回复
热议问题