Live data from Hacker News

Recursive macros in C, demystified (once the ugly crying stops)

h4x0r.org

51–60 of 90 posts

Re: Recursive macros in C, demystified (once the ugly crying stops)

#51
post #31

Is this a DoS risk - code that sends your build chain into an infinite loop?

From a DoS risk perspective there is no practical difference between an infinite loop, or a finite but arbitrarily large loop, which was always possible. For example, this doesn't work: #define DOUBLE(x) DOUBLE(x) DOUBLE(x) DOUBLE(x) That would only expand once and then stop because of the rule against repeated expansion. But nothing prevents you from unrolling the first few recursive expansions, e.g.: #define DOUBLE…

To do this as efficiently as possible, it's probably worthwhile to use a higher radix and shorter macro names. For example:

    $ cc -E -  #define A(x) x x x x x x
    > #define B(x) A(x) A(x) A(x) A(x)
    > #define C(x) B(x) B(x) B(x) B(x)
    > C(Noooooo)
    > .

Re: Recursive macros in C, demystified (once the ugly crying stops)

#52
post #7

The behavior of C macros is actually described by a piece of pseudocode from Dave Prosser and it is not in the standard: * https://www.spinellis.gr/blog/20060626/ * https://www.spinellis.gr/pubs/jrnl/2006-DDJ-Finessing/html/S... * https://gcc.gnu.org/legacy-ml/gcc-prs/2001-q1/msg00495.html

Honestly, it feels like something like this should have been put in the standard instead of all the English prose that ended in the section about the preprocessor expansion. Yeah, it's not pretty, but at least it requires way less skill in hermeneutics to understand correctly.

Re: Recursive macros in C, demystified (once the ugly crying stops)

#53
genuinely remarkable, the altogether perhaps even productive mischief you can get up to, especially with `__VA_OPT__` becoming a proper standard in both C and C++ so you don't have to feel dirty about using it.

i recently made use of plenty of ugly tricks in this vein to take a single authoritative table of macro invocations that defined a bunch of pixel formats, and make them graduate from defining bitfield structs to classes with accessors that performed good old fashioned shifts and masks, all without ever specifying the individual bit offsets of channels, just their individual widths, and macro magic did the rest. no templates, no actual c++, could just as feasibly produce pure c bindings down the line by just changing a few names.

getting really into this stuff makes you stop thinking of c function-like macros as functions of their arguments as such, but rather unary functions of argument lists, where arity roughly becomes the one notion vaguely akin to typing in the whole enterprise, or at least the one place where the compiler exhibits behaviour resembling that of a type checker. this was especially true considering the entries in the table i wound up with were variadic, terminating in variably many (name, width) parenthesised tuples. and i just... had the means to "uncons" them so to speak. fun stuff.

this is worth it, imo, in precisely one context, which is: you want a single source of truth that defines fiddly but formulaic implementations spread across multiple files that must remain coordinated, and this is something you do infrequently enough that you don't consider it worthwhile introducing "real" "big boy" code gen into your build process. mind, you usually do end up having to commit to a little utility header that defines convenient macros (_Ex and such in the article), but hey. c'est la vie. basically x macros (https://en.wikipedia.org/wiki/X_macro) on heart attack quantities of steroids.

Re: Recursive macros in C, demystified (once the ugly crying stops)

#54

    #define _H4X0R_CONVERT_ONE(arg)                  \
        ((union { unsigned long long u; void *v; }){ \
            .u = (unsigned long long)arg,           \
    }).v
Couldn't this be just

    #define _H4X0R_CONVERT_ONE(arg) (void*)(uintptr_t)(arg)
?

Also, thanks, now I can finally use

    void my_printf(const char *fmt, void* args[], size_t argc);
    
ergonomically:

    #define my_printf(fmt, ...) (my_printf)((fmt), \
        (void*[]){ H4X0R_VA_VOID_STAR_CONVERT(__VA_ARGS__) }, \
        H4X0R_VA_COUNT(__VA_ARGS__))

    int main(int argc, char **argv) {
        my_printf("int: %d, ptr: %p, str: %s, missing: %d\n", 42, argv, "Hello world!");
    }

    $ gcc test.c && ./a.out
    int: 42, ptr: 0x7FFF46AA3E78, str: Hello world!, missing: %!d(MISSING)
Funnily enough, the difference between passing ... and locally-allocated void*[] is basically who has to spill the data to the stack, the caller or the called function.

Re: Recursive macros in C, demystified (once the ugly crying stops)

#55

#define _H4X0R_CONVERT_ONE(arg) \ ((union { unsigned long long u; void *v; }){ \ .u = (unsigned long long)arg, \ }).v Couldn't this be just #define _H4X0R_CONVERT_ONE(arg) (void*)(uintptr_t)(arg) ? Also, thanks, now I can finally use void my_printf(const char *fmt, void* args[], size_t argc); ergonomically: #define my_printf(fmt, ...) (my_printf)((fmt), \ (void*[]){ H4X0R_VA_VOID_STAR_CONVERT(__VA_ARGS__) }, \ H4X0R_…

Well, I've done it that way if I'm willing to limit myself to pointers or ints up to a pointer size, but that doesn't work with floats or doubles, for instance.

Ergonomically, I have tended to start using _Generic for static type checking where possible, and that pushes me more toward to avoiding arrays in types for this kind of thing.

Re: Recursive macros in C, demystified (once the ugly crying stops)

#56
post #30

Earlier quoted context omitted.

Wow, you are a braver person than I. Well done.

Thank you. I am actually perversely proud of it.

This was back in 80s when you were working on c compilers? That’s an interesting story (80s compiler scene and what you worked on) I’ve picked up bits and pieces over the years, have you written it up anywhere? Would be benefit for many I think.

Re: Recursive macros in C, demystified (once the ugly crying stops)

#57
One can also (ab)use the build system to run arbitrary preprocessing steps with any language over the "C" input. You can have recursive macros by using M4 or Perl or Python or some other language to expand them, converting your "foo.c.in" into a "foo.c" to hand off to the C preprocessor & compiler. It still feels dirty, but it's often much easier to understand & debug.

Re: Recursive macros in C, demystified (once the ugly crying stops)

#58

One can also (ab)use the build system to run arbitrary preprocessing steps with any language over the "C" input. You can have recursive macros by using M4 or Perl or Python or some other language to expand them, converting your "foo.c.in" into a "foo.c" to hand off to the C preprocessor & compiler. It still feels dirty, but it's often much easier to understand & debug.

Yes, 100%. And since CPP doesn't actually understand C, it's not too hard to do some lightweight preprocessing that requires some real additional parsing.

But while CPP is pretty finicky and not very modern, getting such things working seamlessly with C build systems can be vastly worse (though better than the days where the GNU tools were ubiquitous).

I tend to find meson easy to use compared to all the others, and do this kind of thing, but it's still difficult and brittle.

Re: Recursive macros in C, demystified (once the ugly crying stops)

#60

Related: The Preprocessor Iceberg https://jadlevesque.github.io/PPMP-Iceberg/ There you can find a recursive macro expansion implementation (as a gcc hack) that fits on a slide: #2""3 #define PRAGMA(...) _Pragma(#__VA_ARGS__) #define REVIVE(m) PRAGMA(push_macro(#m))PRAGMA(pop_macro(#m)) #define DEC(n,...) (__VA_ARGS__) #define FX(f,x) REVIVE(FX) f x #define HOW_MANY_ARGS(...) REVIVE(HOW_MANY_ARGS) \ __VA_OPT__(+1 FX(…

You know you're in for a wild ride when the `do { ... } while(0)` hack isn't even on the iceberg.
Post reply on HN