PunkyMunky64
4 min readFeb 8, 2021

--

As you might know, in C/C++, you can use the #define header to make “macros” that the compiler replaces in your code before it compiles. These macros act a lot like a normal function in many ways, however they have some key differences you should be aware of, two in specific which I’m about to explain could cause errors. The syntax for one of these macros looks something like this:

#define addone(number) (number+1)

What this line in the code header does is it tells the compiler to go to every usage of addone() in the code, and replace it with (number+1), where “number” is the argument you passed to addone() (also, note the parentheses around “number+1” because without them you could have problems with PEMDAS/operator precedence when the value gets inlined, similar to a point about to be made). However, notice the absence of an argument type like a normal function would have. This is because normal functions use a different protocol for passing arguments: pass by value. In normal functions, the syntax would look something like this: int addoneNormalFunction(int number){return number+1;} What happens with one of these functions is whenever you call addoneNormalFunction(), whatever you pass as an argument gets copied to a new local variable called number. This is unlike the #define’d macro, because the macro simply uses the “number” argument as an alias for whatever you pass as an argument. This is because of the way the compiler unwraps the macros before compiling: the actual program doesn’t do any passing-by-value or reference or whatnot, the compiler just swaps out “number” for whatever the user put in the macro argument.

There are a couple main points here. The first of which is that you should add extra parentheses to be safe. Wherever in your macro you are referring to an argument, you should nest it in parenthesis, otherwise something the user puts as an argument could mess with the PEMDAS/operator precedence. For example, let’s make a different macro:

#define multiplybytwo(number) (number*2)

As innocent as this looks, what if the user calls something like “multiplybytwo(x+1)”? Then we wind up with the compiler changing the macro to the following:

x+1*2

AAGH! x won’t get multiplied at all, only the “1”! The way the return value just go slotted in there did something that was not intended. To fix this, we simply put “number” from the return value of the macro in parenthesis, like this:

#define multiplybytwo(number) ((number)*2)

Okay, now for the second point! This is just as important as the first, but unfortunately, there’s no easy way to fix this in your macros, it’s more of a thing the user has to remember: careful with what you put within the macro argument space, especially if you are nesting a function in it! Let’s start with a new example. What if you were passing the return value of a function to your macro, and you nested it as an argument? This might work, but be aware that because of the way that the compiler simply replaces all the passed arguments with what the user inputted, it would call the function each time the argument name was referred to! Here’s an example of how this could go wrong:

#include <iostream>

#define abs(num) ((num) < 0 ? (-(num)):(num))

int IncrementAndReadScore(void);

int score = 10;

int main(){

std::cout << abs(IncrementAndReadScore()) << ‘\n’;

}

int IncrementAndReadScore(void){

score++;

return score;

}

In this example, firstly we make a macro for an absolute value function. Notice it is implemented: it checks if the number is less that 0, and uses a conditional operator to return the correct value. It also uses parentheses around each instance of the argument name like explained in the last point. Next we make a variable called score, initialize it to 10, and prototype the function IncrementAndReadScore(). This function simply increases score by 1 then returns it, as the name suggests. Finally, in the main loop, we output the absolute value of IncrementAndReadScore(). An unknowing would expect this to output 11, and with reason; it would take 10, increment it once, pass that value to the abs function, and then get 11. However, you can see where this is going. IncrementAndReadScore() never gets called before the abs function; the compiler just replaces the chunk with the abs macro. This replaces num with IncrementAndReadScore(), and since it has to check the value of num and then return num, it calls IncrementAndReadScore() twice, which outputs 12!! This is not what many would expect, as the inner implementation of abs varies how many times the nested function was called. Weird!

Anyway, the moral is you should only use these Preprocessor-defined macros if you know what you’re doing. I hope this short article banishes any related future bugs you’d have :)

--

--