3

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?

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • Why do you use the pointer-to operator `&`? It doesn't make any sense. – Some programmer dude Jan 26 '23 at 13:04
  • There's always boost PP for your weird macro needs, but before that, why do you need this? – Passer By Jan 26 '23 at 13:05
  • @Someprogrammerdude - Can the question not be answered as stated without going further into my intent? Is "*because I want to define a list of variables once, and automatically derive a list of their string names and their `void*` pointers automatically, without me having to re-type them manually*" enough? – sdbbs Jan 26 '23 at 13:08
  • 1
    The problem is that string literals already decay to pointers, so using the `&` operator you will likely get the wrong type. If you want to be able to use it for strings, integers or something else then it's not really possible to use it in a nice and generic way. If you tell us the actual and underlying problem you need to solve, and why you think an array of `void *` pointers would solve that problem, perhaps we might be able to help you in a better way to solve that actual and underlying problem. – Some programmer dude Jan 26 '23 at 13:20

3 Answers3

4

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;
}
nielsen
  • 5,641
  • 10
  • 27
  • Excellent - thanks! I found also a way through other SO approaches (below), but this one is much cleaner - much appreciated! – sdbbs Jan 26 '23 at 13:59
  • @sdbbs You are very welcome. You may also benefit from the extension I describe in the answer to this other question in case you need to treat variables of different type differently: https://stackoverflow.com/questions/70714601/is-there-a-better-way-to-compare-two-c-structs – nielsen Jan 27 '23 at 07:11
0

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", };
sdbbs
  • 4,270
  • 5
  • 32
  • 87
0

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]);
}
Lundin
  • 195,001
  • 40
  • 254
  • 396