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

Time:01-27

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?

CodePudding user response:

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;
}

CodePudding user response:

Ok, seems I found an answer, mostly thanks to Is it possible to iterate over arguments in variadic macros? (see also Expand a macro in a macro); this is test_cpp.c:


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


// Make a FOREACH macro
#define FE_0(WHAT)
#define FE_1(WHAT, X) WHAT(X) 
#define FE_2(WHAT, X, ...) WHAT(X)FE_1(WHAT, __VA_ARGS__)
#define FE_3(WHAT, X, ...) WHAT(X)FE_2(WHAT, __VA_ARGS__)
#define FE_4(WHAT, X, ...) WHAT(X)FE_3(WHAT, __VA_ARGS__)
#define FE_5(WHAT, X, ...) WHAT(X)FE_4(WHAT, __VA_ARGS__)
//... repeat as needed

#define GET_MACRO(_0,_1,_2,_3,_4,_5,NAME,...) NAME 
#define FOR_EACH(action,...) \
  GET_MACRO(_0,__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1,FE_0)(action,__VA_ARGS__)

#define XSTR(x) STR(x)
#define STR(x) #x

// original: https://stackoverflow.com/a/11994395
//#define QUALIFIER(X) X::
//#define QUALIFIED(NAME,...) FOR_EACH(QUALIFIER,__VA_ARGS__)NAME

#define POINTERER(X) &X,
#define POINTERIFIED(NAME,...) FOR_EACH(POINTERER,__VA_ARGS__)NAME

// leave first argument (from original QUALIFIED) empty here!
#define vptrs POINTERIFIED(,VARLIST) 

void* vars_ptr[] = { vptrs };

//#define DQUOTE " // no need for DQUOTE;
// if we just use the number/hash sign, X gets automatically quoted with double quotes!
// (don't forget the comma at the end!)
#define DQUOTERER(X) #X,
#define DQUOTERIFIED(NAME,...) FOR_EACH(DQUOTERER,__VA_ARGS__)NAME

// leave first argument (from original QUALIFIED) empty here!
#define vnames DQUOTERIFIED(,VARLIST) 
char vars_names[][16] = { vnames };

... and this is the output of the preprocessor:

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

#define VARLIST var_one, var_two, var_three, var_four

#define FE_0(WHAT)

#define FE_1(WHAT,X) WHAT(X)

#define FE_2(WHAT,X,...) WHAT(X)FE_1(WHAT, __VA_ARGS__)

#define FE_3(WHAT,X,...) WHAT(X)FE_2(WHAT, __VA_ARGS__)

#define FE_4(WHAT,X,...) WHAT(X)FE_3(WHAT, __VA_ARGS__)

#define FE_5(WHAT,X,...) WHAT(X)FE_4(WHAT, __VA_ARGS__)

#define GET_MACRO(_0,_1,_2,_3,_4,_5,NAME,...) NAME

#define FOR_EACH(action,...) GET_MACRO(_0,__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1,FE_0)(action,__VA_ARGS__)

#define XSTR(x) STR(x)

#define STR(x) #x

#define POINTERER(X) &X,

#define POINTERIFIED(NAME,...) FOR_EACH(POINTERER,__VA_ARGS__)NAME

#define vptrs POINTERIFIED(,VARLIST)

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

#define DQUOTERER(X) #X,

#define DQUOTERIFIED(NAME,...) FOR_EACH(DQUOTERER,__VA_ARGS__)NAME

#define vnames DQUOTERIFIED(,VARLIST)

char vars_names[][16] = { "var_one","var_two","var_three","var_four", };

So, ultimately, I got my output as desired - and no preprocessor errors/warnings, as far as I can tell:

void* vars_ptr[] = { &var_one,&var_two,&var_three,&var_four, };
char vars_names[][16] = { "var_one","var_two","var_three","var_four", };

CodePudding user response:

As already mentioned, you are more or less literally describing the purpose of "X macros".

An alternative, arguably somewhat more readable way of writing them is to first declare a macro list, then pass a macro to that list. As in

  • "here is a function-like macro, run it with all the arguments listed", rather than
  • "here is the function-like macro X, run macro X with all the arguments listed".

This allows you to give macros meaningful names, optionally group all macros belonging to the list somewhere, and it eliminates the need to #undef.

#include <stdio.h>

// note the absence of commas
#define VARLIST(X) \
  X(var_one)       \
  X(var_two)       \
  X(var_three)     \
  X(var_four)      \

int main (void)
{
  char varnames[][16] =
  {
    #define VARLIST_QUOTED(name) #name, /* "stringification operator" */
    VARLIST(VARLIST_QUOTED) /* no semicolon or comma here */
  };
  
  #define VARLIST_DECL_VARS(name) int name; /* declare a bunch of int */
  VARLIST(VARLIST_DECL_VARS)

  int* const pointers[] = 
  {
    #define VARLIST_PTR(name) &name, /* declare an array of pointers to previous ints */
    VARLIST(VARLIST_PTR)
  };

  var_two = 123;
  printf("%s has value %d and address %p\n", 
         varnames[1], 
         var_two, 
         pointers[1]);
}
  • Related