Suppress C Macro Variable Substitution

2019-06-17 03:43发布

I have this bit of code (part of an interpreter for a garbage-collected Forth system, actually):

#define PRIMITIVE(name) \
    do \
    { \
        VocabEntry* entry = (VocabEntry*)gc_alloc(sizeof(VocabEntry)); \
        entry->code = name; \
        entry->name = cstr_to_pstr(#name); \
        entry->prev = latest_vocab_entry; \
        latest_vocab_entry = entry; \
    } \ 
    while (false)

PRIMITIVE(dup);
PRIMITIVE(drop);
PRIMITIVE(swap);
// and a lot more

but there's a problem: in the line

entry->name = cstr_to_pstr(#name);

the name field is substituted for dup, drop, swap, and the rest. I want the field name to not be substituted.

So, is there any way to solve this, other than simply renaming the macro argument?

For an answer, please explain if there is, in general, a way to suppress the substitution of a macro argument name in the macro body. Don't answer "just do it this way" (please).

3条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-06-17 04:18

No, there is not.

To see why, you need to consider the way macro expansion actually happens. Expanding a function-like macro requires three main steps:

  1. the arguments to the macro are fully expanded, unless the macro uses the # or ## operators on them (not relevant in the example since they're single tokens)
  2. the entire replacement list is scanned, and any occurrence of a parameter name is replaced by the corresponding argument
  3. after step 2 is complete, the expanded replacement list is itself rescanned, and any macros appearing are expanded at this point

This is outlined in standard section 6.10.3 (C11 and C99).

The upshot of this is that it is impossible to write some kind of macro that can take name and abuse the suppression-rules of '##' or anything like that, because the replacement step in the body of PRIMITIVE must run completely before any of the macros within the body are allowed their turn to be recognised. There is nothing you can do to mark a token within the replacement list for suppression, because any marks you could place upon it will only be examined after the replacement step has already run. Since the order is specified in the standard, any exploit you find that would let you mark a token in this way is a compiler bug.

Best I can suggest if you're really set on not renaming the macro argument is to pass na and me as separate arguments to a concatenation macro; the token name will only be formed after replacement is done and the list is no longer being examined for parameter names.

EDIT wish I typed faster.

查看更多
孤傲高冷的网名
3楼-- · 2019-06-17 04:20

You can define a different macro to expand to name, like this:

#define Name name

and change the name field in the PRIMITIVE macro to use the new macro, like this:

#define PRIMITIVE(name) \
    do \
    { \
        VocabEntry* entry = (VocabEntry*)gc_alloc(sizeof(VocabEntry)); \
        entry->code = name; \
        entry->Name = cstr_to_pstr(#name); \
        entry->prev = latest_vocab_entry; \
        latest_vocab_entry = entry; \
    } \ 
    while (false)

Other than using something different from the parameter name in the macro body or changing the parameter name, there is no other way to do this in the C language. Per C 2011 (N1570) 6.10.3.1 1, when a function-like macro is recognized, parameter names are immediately substituted except when # or ## is present, and there no other exceptions:

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.

The # token changes the parameter name to a string, which is no use in this situation. The ## token expands the parameter name and pastes it together with an adjacent token, which is also no use in this situation.

查看更多
Anthone
4楼-- · 2019-06-17 04:27

No, there is no way to suppress the replacement within a macro's body of a token identical to that of a declared argument to said macro. Every possible solution short of jumping into the preprocessor code will require you to rename something, either the argument name or the name of the field (potentially just for purposes of that macro, as Eric's answer does).

查看更多
登录 后发表回答