Macro functions are kind of like regular functions (except when they're not)
I unabashedly love C. I opened the C Standard for something yesterday. I am not an expert in C, but it is most assuredly my favorite programming language. There’s this silly article someone sent me called If Programming Languages Were Harry Potter Characters. Whoever wrote this article decided that C is Voldemort. Well, if that’s the case, just call me a Death Eater.
C is cool for many reasons, one of them being the ability to harness the power of the preprocessor. I have heard rumors that C++ fans don’t like the preprocessor, which is a shame since macros can be awesome.
All a macro does is perform a text replacement in your file.
For example, you can make your code more readable by defining constants
#define EXIT_FAILURE -1
EXIT_FAILURE shows up in your code, the preprocessor will replace that with
-1 before shipping it off to the compiler.
If you know C, you’re thinking “duh, Michelle, obviously. My first word was ‘macro’. Why did you even write this?”
Well, keep reading. Here there be dragons. (well, not really dragons, just a big “gotcha” that even experienced programmers miss).
Macros can take parameters, which are called “macro functions”. A macro function is a parameterized macro. I consider this to be a misleading name, because they’re not functions at all.
Consider the following macro function to calculate the area of a right triangle:
#define RIGHT_TRIANGLE_AREA(base, height) \ base * height * .5
In your code, you could write
and the preprocessor would kindly insert
4 * 3 * .5 for you, which would get compiled into
Maybe you’ve spotted the “gotcha” and you already know where this is going.
However, Jojo’s Sleazy Coding Operation has different ideas on how to use this macro. For whatever reason, Jojo decides to calculate his triangle area like this:
RIGHT_TRIANGLE_AREA(4, 4 - 1)
If this were a function, it would pass
3 as the second parameter. However, this is NOT a function, this is a macro! Macros just do text replacement, remember? What ends up happening is
4 * 4 - 1 * .5
Due to order of operations, that gives us…
That’s not what we want to happen at all. Jojo’s Sleazy Coding Operation is up in flames because in this case, “do what I say” != “do what I mean” and you’ve given him a terrible macro which has a bug that’s extremely hard to track down.
Put any macro parameters in parentheses.
I’ll say it again for the people in the back.
PUT YOUR MACRO PARAMETERS IN PARENTHESES!
Check it out.
#define RIGHT_TRIANGLE_AREA(base, height) \ (base) * (height) * .5
When Jojo uses this in his project as before, the preprocessor will write it out like this.
(4) * (4 - 1) * .5
He gets the correct value of
Now, say Jojo wants to do this.
24 / RIGHT_TRIANGLE_AREA(4, 3)
We’d expect to get
24 / 6 = 4, right? Not so fast. Let’s look at it through the eyes of the compiler after the preprocessor has done its magic.
24 /(4) * (3) * .5
So that equals…
9. Yikes. Not at all what we want to happen. You can get yourself in all sorts of rabbit holes when you think of parameterized macros like functions.
You can also think of the case where Jojo’s Sleazy Coding Operation wants to cast the result to something. In this case, only the
4 will get cast. Depending on the data type he’s trying to cast it to, he might get lucky and it’ll work anyway, or something completely unexpected will happen and cause yet another impossible bug to track down.
Are you listening?
Put your entire parameterized macro in parentheses. Or else.
#define RIGHT_TRIANGLE_AREA(base, height) \ ((base) * (height) * .5)
Now we’re in business.
24 / RIGHT_TRIANGLE_AREA(4, 3) will finally give us the
4 we’re looking for.
I’ve created this type of bug in macros myself (but I caught myself before shipping it). I’ve seen (and fixed) this exact bug in existing code and engineers far more senior than I overlooked it until I brought it up. Depending on how complex your macro is, you may want to consider using an actual method. If your macro starts to look like a real function, maybe you ought to use one, because (say it with me) macro functions aren’t real functions!
Macro function users:
Don’t trust that the creator of your macro didn’t bungle it - check it yourself if possible and be careful about what you use as parameters. If at all possible, do any computations before passing a value to the macro. Here’s lookin’ at you, Jojo.
Macro function writers:
Don’t expect that the Jojos of the world will follow that advice. Think about what you’re doing and always, always put the parameters and macro itself in parentheses (in case you missed it the first zillion times around). Secondly, think about whether or not a macro is really what you want and consider using a real function if the macro is getting a little crazy.
Never think of macro functions as real functions. Think of them as “parameterized macros” where all the same preprocessor text replacement rules apply as usual.