You don't have to keep the preprocessing of files as part of the mainline build, but there's something to be said for it - sort of "make GENERATE_ALL_THE_THINGS" might run the preprocessing { Python/Tcl/Perl/bash/even 'C' } scripts for you.
If the generators just emit .h files, that can be pretty good. You're still left with something #ifdef-ey to select them, based on #defines or -D options.
You might even go so far as to dynamically load these tables if that can make sense. The ld linker can directly link in blobs.
Macros should always be the absolute last resort to doing anything. Stepping through code in gdb with some "creative" macro-based API is almost as bad as C++.
More modern compilers allow using non-static "const" constructs to do much of the same, which is a great improvement.
To wit:
#define in_bounds(lower,x,upper) ((x <upper) && (x >lower))
vs. const bool in_bounds = ((x > lower) && ( x < upper ));
Macros can be used effectively for reduction in strength, so long as you're not too clever about it.