One strange phenomenon when coding in C is using macros.
This is not something which can be seen in modern programming languages (other than C++). And that is for a reason.
Using macros can be extremely unsafe and they hide a lot of pitfalls which are very hard to find. However, as a C or C++ programmer, inevitably, you will encounter macros in your coding life. Even if you don’t use them in your own project, there is a high chance you will encounter them somewhere else, such as a library.
Your duty is to understand why using this programming feature is dangerous and what dangers it holds. If you don’t, then you can run into some pretty nasty errors which are hard to debug and discover.
Macros vs. Functions
The first time I got introduced to macros, they seemed like normal function calls. Sure, they have a bit of a strange syntax, but they “behave” like normal functions.
Then what is the difference?
Well, macros are a text processing feature. What happens once you build your program is that all occurrences of macros are “expanded” and replaced by the macro definitions.
What does this look like?
gets preprocessed into
On the other hand, when functions get called, they have a new allocated stack frame for them and they act independently of the place where they were called.
If you define a variable var inside function foo, and it calls a function bar, which defines its own variable var, then there will be no error, because the variable defined in bar is different from the variable defined in foo.
On the other hand, if you try to do this with a macro, then you will get a compilation error, since the variable defined in the macro will be in the same function as the other variable:
Apart from that, functions give you type checking. That means that if a function expects a string, but you give it an int, you will get an error (or at least a warning, depending on the compiler).
Macros, however, just substitute the argument you pass them.
And finally, macros can’t be debugged. When you use a function, that function can be stepped through by the debugger. The macro cannot.
So if you have a macro which fails somewhere, the only way you can find out what the problem is is by looking at its definition and trying to find out what happened.
There is one advantage of using macros over functions – efficiency.
A macro is faster than a function.
As I mentioned, when you call a function, it has to allocate some data. That data must be allocated on the stack and that takes time.
Macros don’t have that overhead.
This benefit can make the difference in a highly-constrained system. Such as a very old micro-controller.
But even nowadays, programmers make some optimizations by using macros for small procedures.
However, there is an alternative for this in C99 and C++ – that’s inline functions.
When you add the inline keyword in front of a function, you are hinting the compiler to embed the function body inside the caller (just like a macro).
But what’s great about inline functions is that they can be debugged. And they have type checking.
However, the inline keyword is merely a hint to the compiler, it is not a strict rule and he can decide to ignore that hint.
But, there is an attribute (always_inline) in gcc, which forces the compiler to inline the function.
Inline functions are a great feature, which further makes the usage of functions over macros preferable.
There are, however, some things you can’t achieve using functions.
When to use macros in C
Passing default arguments
C++ has a pretty neat feature you don’t get in C. That’s default arguments. This is used when you have an argument which can be optionally set:
However, you don’t have this in C. But you can simulate it via a macro like this:
This code is pretty safe and does not hide any potential pitfalls such as the ones we will discuss.
Of course, you can achieve the same thing using functions, but there is no need to have the overhead of function call for something like this. A macro is sufficient.
Using debug strings
Some compilers offer debugging strings, which aren’t as helpful when used in functions: __FILE__, __LINE__, __func__.
The reason why they won’t work as you’d want in a function call is that it will print the line of the program in that function, rather than the line in the function that called it. Same goes for __func__.
You can use these to define macros used for debugging your code like this:
This is actually a pretty cool way of logging what’s going on and I tend to use it in my C projects a lot.
Syntax modification
This is a very powerful feature of macros. Using them, you can create your own syntax.
For example, there is no foreach function in C.
But you can create your own using macros.
Here we have a struct of a linked list. Assuming we have filled it with nodes, we can traverse it using LIST_FOREACH just like we would use foreach in modern languages like C#.
This technique is indeed very powerful and when used properly, can bring you some pretty great results.
Check out the Catch Test Framework library for example.
Other types of preprocessor directives
Apart from macros, there are other preprocessor directives which are pretty useful as well.
Here are some of the most used ones:
#include – Include the contents of a file inside your file
#ifdef – conditional compilation
#define – used for defining constants in C.
#ifdef is crucial when creating header files. This macro is used in order to make sure a header file is only included once:
Apart from that, #ifdef can be used for conditionally compiling blocks of code based on some condition. A great application is outputting debug information only when we are running in debug mode:
#define for constants is pretty useful, although some projects try not to use it by substituting it with const variables and enums.
However, there are drawbacks of using either of those alternatives.
const variables are preferred over macro constants because you have type checking with them. However, in C, those are not true constants.
For example, you cannot use these in a switch-case statement and you can’t use them for defining a static array’s size.
Note: In C++, const variables are real constants (you can use them in the above cases) and it is strongly preferred to use them instead of #define constants.
Enumerations, on the other hand, are real constants. They can be used in switch-case statements and for defining an array’s size.
However, their drawback is that with them, you can only define integers. You cannot use them for string constants and floating-point constants.
That is why, in the end, #define constants are your only option if you want a unified way of creating constants in your code.
So as you can see, macros can still be useful in some contexts.
But if you have to use them – beware!
The pitfalls
Pitfall 1: Not putting the brackets
The most common error I have seen when using macros is to forget putting brackets around arguments in macro definitions.
Why is this an issue?
Well, macro calls get substituted directly into your code. That can cause some pretty nasty side effects because of operator precedence!
In this example, we are trying to evaluate MULTIPLY(x + 5), which should result in 50. Instead, we get a result of 30.
The reason for this is that macros directly substitute the text inside our code. So after substitution we get:
MULTIPLY(x + 5) —> (x + 5 * 5)
And as you know, multiplication has precedence over addition. That is why we get the erroneous result.
How to do it correctly?
Pitfall 2: Using increment/decrement operations
Sometimes, when we want to act cool when writing our code, we tend to embed increment and decrement operations in other statements.
But when you use this with a macro call, then you might be in for some trouble!
Let’s say you have this code:
Here, you might expect that x will get incremented and will be equal to 6 and the result will be 5. Wrong:
The reason for this is again – macro substitution:
ABS(x++) —> ((x++) < 0 ? -(x++) : (x++))
Here, we increment x once in the check and again when assigning the result. That is why we get wrong results.
Pitfall 3: Passing function calls
It’s pretty common to compose functions in our code. That means to give the output of a function as an input to another function. And often times, we do it like this:
And that’s OK. But when you do that with a macro, you might get some serious performance issues!
Let’s say you have this code:
Here, we have a recursive function sum_chars. We call it once on the first string and a second time on the second string.
But if we pass these function calls as arguments of a macro, then we will get 3 recursive calls instead of 2.
For large data structures, such a flaw can cause a big performance bottle neck.
Especially if the macro is used inside the recursive function!
Pitfall 4: Multiple line macros
Sometimes, we try to save a bit of file space by not putting brackets when we write loops and if statements.
But if we make a macro call inside that block and that macro expands to multiple lines, that will result in something you don’t expect
Here, we skip adding braces on the first while and since the macro expands to more than one line, only the first expression in the macro gets executed in the while loop.
Hence, this results in an infinite loop since the index never gets incremented.
The proper way to define a macro like this is to surround the contents with a do-while loop like this:
This was the last one.
As you can see, there are a lot of issues with macros.
That is why we tend not to use them if we can.
But if you decide to use them even so, then you should at least follow some kind of naming convention in order to warn developers that they are dealing with macros.
Good practices
As you can see, there is a huge difference between functions and macros. And without the knowledge of this, you might get in trouble.
In order to minimize the issues aroused by using macros, I suggest using a common standard for defining macros in your code.
What exactly that will be doesn’t matter.
I have seen projects in which all macro definitions are declared UPPERCASE.
In other places, I have seen the pattern of prefixing a macro name with a ‘m’, in order to know you are dealing with a macro.
Whatever your choice is doesn’t matter. It seems the most popular one is to define all macro names uppercase. So you can follow that convention.
But please follow a convention. As you can see, there are a lot of issues you might encounter when using macros in your code and you should warn programmers that they are dealing with a macro somehow.
Conclusion
Hopefully, now you understand that there is a difference between functions and macros.
In most cases, prefer using functions as macros hide some serious side effects.
But if you decide to use them for something, then please be aware of the pitfalls you might get into and follow a good convention for defining macros.