The Macro That Took Five Tries to Even Compile
A three-argument max macro should be a two-minute exercise. Mine took five tries just to get the preprocessor to accept it, and two more after that to get the right answer out.
I saw the kernel's MAX macro somewhere in a header and copied the shape of it without really getting why it was built that way — something about not evaluating the arguments twice, which sounded like a good enough reason to just try the same trick myself. Three arguments instead of two. How much harder could that be. Five compile attempts later, I found out.
The setup
The version I was copying looked roughly like this:
#define MAX3(a, b, c) ({ \
int _a = (a); \
int _b = (b); \
int _c = (c); \
int _r; \
_r = (_a > _b) ? (_a > _c ? _a : _c) : (_b > _c ? _b : _c); \
_r; \
})I didn't fully understand the ({ ... }) part when I started typing it out. I just knew it was "the kernel way" of writing a multi-line macro that still gives you back a value, and I wanted it under my fingers before I ran into it for real somewhere else in this curriculum.
What went wrong
First attempt, I just didn't put backslashes at the end of the lines. I don't think I forgot on purpose — I don't think I'd actually clocked that they were doing anything. The compiler did not agree, and threw errors at code two or three lines below where the macro had, apparently, already ended without me.
Second attempt, I used / instead of \. I don't have a good explanation for that one. My hand just did it.
Third and fourth attempts, I had backslashes on every line except int _r; — twice, on two separate full rewrites of the macro. It's a short line and it doesn't look like it's doing anything important, which is apparently exactly why I kept skipping it.
Then I wrote _r;\}) on what I thought was the fix — a backslash sitting between the semicolon and the closing braces, not at the actual end of the line. GCC said stray '\' in program, which, fair. I typed into my own notes at the time: "idk which char is that." Not proud of that sentence, but it's the most honest one in this post.
Once the backslashes were finally all sitting where they belonged, two more bugs were waiting. I'd written (_a > _c) ? _a;_c; — semicolon where a colon belonged, probably because every other line in this macro ends in a semicolon and my hand just kept doing that. And even after fixing that one, I'd written the ternary as a bare line that computes something and hands it to nobody — I never assigned it to _r, so the comparison ran and its answer just evaporated.
The fix
_r = (_a > _b) ? (_a > _c ? _a : _c) : (_b > _c ? _b : _c);
_r;Every line but the last ends in \, nothing after it. The ternary uses :. And the result gets assigned to something before the macro hands it back.
I don't think I actually understand statement expressions yet, if I'm being honest with myself — I understand this one macro, because I've now typed it wrong five or six different ways and right exactly once. What I'm taking from this session isn't really a rule about macros. It's more of a warning about how I work: when I'm copying something I don't fully get, I get sloppy on exactly the mechanical stuff — backslash placement, ternary punctuation — because all my attention is going toward the unfamiliar part and none of it toward the part I assume I already know. Next time I catch myself copying a pattern I don't fully understand, that's my cue to slow down on the "easy" half, not speed up.
Notes from readers
Comments — via GitHub