Search code examples
arrayscgccc-preprocessor

Prepend/append string to each element in C preprocessor list?


Ultimately, what I want is this: first, have a list of variable names declared as a C preprocessor macro; say, in test_cpp.c:

#define VARLIST \
  var_one, \
  var_two, \
  var_three, \
  var_four

These would eventually be actual variable names in code - but, of course, the preprocessor does not know (or even has a concept of) that at this time.

To make sure the macro has been parsed correctly, I use this command (awk to get rid of the preamble defines in the gcc -E preprocessor output):

$ gcc -E -dD test_cpp.c | awk 'BEGIN{prn=0} /# 1 "test_cpp.c"/ {prn=1} prn==1 {print}'
# 1 "test_cpp.c"

#define VARLIST var_one, var_two, var_three, var_four

So far, so good.

Now: second, I'd like to use this list - that is, I'd like to (pre)process it - and prepend and append characters to each element (token) of the VARLIST, so that I end up with the equivalent of the following macros:

#define VARLIST_QUOTED "var_one", "var_two", "var_three", "var_four"
#define VARLIST_PTR &var_one, &var_two, &var_three, &var_four

... which I could ultimately use in code as, say:

char varnames[][16] = { VARLIST_QUOTED };

( ... which then would end up like this in compiled code, inspected in debugger:

(gdb) p varnames
$1 = {"var_one\000\000\000\000\000\000\000\000", 
  "var_two\000\000\000\000\000\000\000\000", 
  "var_three\000\000\000\000\000\000", 
  "var_four\000\000\000\000\000\000\000"}

)

I'm guessing, at this time the preprocessor wouldn't know & is intended to be an "address-of" operator, although I think it has special handling for double quotes.

In any case, I think that such "lists" in the preprocessor are handled via Variadic Macros (The C Preprocessor), where there is an identifier __VA_ARGS__. Unfortunately, I do not understand this very well: I tried the first thing that came to mind - again, test_cpp.c:

#define VARLIST \
  var_one, \
  var_two, \
  var_three, \
  var_four

#define do_prepend(...) &##__VA_ARGS__
#define VARLIST_PTR do_prepend(VARLIST)

void* vars_ptr[] = { VARLIST_PTR };

Then if I run the preprocessor, I get this:

$ gcc -E -dD test_cpp.c | awk 'BEGIN{prn=0} /# 1 "test_cpp.c"/ {prn=1} prn==1 {print}' | sed '/^$/d;G'
test_cpp.c:8:25: error: pasting "&" and "VARLIST" does not give a valid preprocessing token
    8 | #define do_prepend(...) &##__VA_ARGS__
      |                         ^
test_cpp.c:9:21: note: in expansion of macro 'do_prepend'
    9 | #define VARLIST_PTR do_prepend(VARLIST)
      |                     ^~~~~~~~~~
test_cpp.c:11:22: note: in expansion of macro 'VARLIST_PTR'
   11 | void* vars_ptr[] = { VARLIST_PTR };
      |                      ^~~~~~~~~~~
# 1 "test_cpp.c"

#define VARLIST var_one, var_two, var_three, var_four

#define do_prepend(...) & ##__VA_ARGS__

#define VARLIST_PTR do_prepend(VARLIST)

void* vars_ptr[] = { &var_one, var_two, var_three, var_four };

It does show an error - but ultimately, the preprocessor did prepend a single ampersand & to the first variable in the array vars_ptr, as I wanted it to ...

The question is, then: can it prepend an ampersand & to all the entries in the list VARLIST without errors (and likewise, can it both prepend and append a double quote " to all the entries in the list VARLIST without errors) - and if so, how?


Solution

  • Sounds like a job for X macros:

    #include <stdio.h>
    
    #define VARS \
      X(var_one) \
      X(var_two) \
      X(var_three) \
      X(var_four)
    
    // Define all the variables as ints (just for the example)
    #define X(V) int V=0;
    VARS
    #undef X
    
    // Define the array of variable pointers
    #define X(V) &V,
    void* vars_ptr[] = { VARS };
    #undef X
    
    // Define the array of variable name strings
    #define X(V) #V,
    const char *var_names[] = { VARS };
    #undef X
    
    // Set a few variable values and print out the name/value of all variables
    int main()
    {
        var_one = 9;
        var_two = 2;
        for(unsigned i = 0; i < sizeof(var_names)/sizeof(var_names[0]); i++)
        {
            printf("%s=%d\n", var_names[i], *(int *)vars_ptr[i]);
        }
    
        return 0;
    }