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