While most naming conflicts in C++ can be solved using namespaces, this is not true for preprocessor macros.
Macros cannot be put into namespaces. If you try to declare a new class called Stream
, but somewhere in a header you include is a macro called Stream
, things will break. While compiling your code, the preprocessor does simply replace the Stream
token in your class Stream {
declaration with the contents of the #define
statement. You will probably get a really confusing error message, and it would take time and energy to find the actual problem.
People often overuse macros for almost everything, especially those who develop software for micro controllers. They strongly believe it will save RAM, speed up the code or make it more flexible. Often none of these three things are true. Actually each additional macro is a risk for name conflicts and makes the code less readable. You should reduce the use of macros to the absolute minimum, and especially avoid macros in your header files.
Avoid Macros for Constants
There are many people using macros for constants. They believe that because the macro will be expanded before the compiler will actually “see” the value, it will produce better code. Instead of blind believe things, this can easily be tested. I will use an Arduino Uno for the tests, a simple 8 bit processor which uses easy to understand machine code.
First an example of bad code using a macro definition as a constant.
Outdated Bad Code:
#define MY_CONSTANT 10 uint8_t doSomething() { uint8_t x = 0; x += MY_CONSTANT; return x; }
The produced Arduino sketch has 450 bytes, and uses 9 bytes of dynamic memory. The compiler will produce very simple machine code for the function as shown below.
ldi r24,lo8(10) ret
You can spot the 10
where the compiler inserted the constant. It simplified the code and removed the addition, because it is clear at the end the variable will always be 10.
But let us compare the code if we use a constant variable. The compiler usually does not reserve memory for simple constant variables, except this will save time and space.
Improved Code:
const uint8_t MY_CONSTANT = 10; uint8_t doSomething() { uint8_t x = 0; x += MY_CONSTANT; return x; }
The produced Arduino sketch has 450 bytes, and uses 9 bytes of dynamic memory. There is no difference in memory usage compared to the bad example. But let us have a look at the produced machine code:
ldi r24,lo8(10) ret
There is absolutely no difference in the produced machine code. But while there is no difference in the result, there is a difference in the code. In the code you add some very important type information to the constant, which the macro definition lacks.
This type allow the compiler to detect problems in your code. E.g. you define a signed integer as a constant, but use it in a unsigned context. This may lead to a problem, but you won’t detect this problem if you are using macros. With a declared constant variable, the compiler will at least give you a warning message.
const
variables will…
- … make the code more readable
- … let the compiler to check the types in your code and generate warnings.
- … provide more information for the compiler to optimise your code.
- … let the compiler to compile you code faster.
Avoid Macros for Header Guards
A long time in the past, it was necessary to use macros to protect headers from being included twice by the preprocessor and compiler. This looked like this:
Bad Ancient Code:
#ifndef MYCLASS_H #define MYCLASS_H // your declarations. #endif
Not only have you the risk of naming conflicts with the macros used for the guard, the compiler has no clue of your intentions and can therefore not optimize its behavior.
But there is the #pragma once
which is supported by all modern compilers which was made to prevent a header included twice in one compiling unit. The compilers implement this in a very safe and efficient way. It is speeding up the compile time, especially for large projects, and it avoids the risk of naming conflicts. If you do not have the need to support compilers which are 10 or more years old, please use code as shown below.
Improved Code:
#pragma once // your declarations.
#pragma once
will …
- … make the code more readable by reducing useless clutter.
- … avoid naming conflicts with other libraries and headers.
- … speed up the compile time.
Avoid Macros for “Flexibility”
You write a library, but you want to make it work with different controllers. Soon people start to declare macros and include lots of #ifdef
, #else
and #endif
statements in the code. There are many disadvantages of this: The first is readability of your code. It is really hard to see which part of your code will actually be executed and which not. Another one are missing compiler checks. Each part which is removed by the preprocessor, is never compiled after all. If this part contains errors, you will never know until you “activate” this code part.
There are many simple and safe alternatives. First, use C++ constants! Have a look at the following example code:
Using Constants
enum ControllerType { ControllerType_Uno, ControllerType_Due }; const ControllerType cController = ControllerType_Uno; uint8_t doSomething() { uint8_t x = 0; if (cController == ControllerType_Uno) { x += 100; } else { x += 80; } return x; }
The assembler code created for the function doSomething()
is shown below. You can clearly see it only contains the constant 100
and the compiler removed the second part – because obviously it will never be used.
ldi r24,lo8(100) ret
Using Different Files
Another simple way to provide multiple implementations is by using different backend files. First you declare the interface of your library/component in a simple header file:
Example.hpp
#pragma once class Example { public: void doSomething(); private: void mcuSpecificPart(); };
Now you add the corresponding implementation file. In the default implementation you only write the code which is shared between all MCUs.
Example.cpp
#include "Example.hpp" void Example::doSomething() { // ... mcuSpecificPart(); // ... }
Now you create additional implementation files for the different MCUs you would like to support.
Example_Uno.cpp
#include "Example.hpp" void Example::mcuSpecificPart() { // ... }
Example_Due.cpp
#include "Example.hpp" void Example::mcuSpecificPart() { // ... }
Depending which MCU you use, you either add the _Uno.cpp
or _Due.cpp
file to your project. This simple abstraction keeps you code organised and does not require any macro at all.
Make Register Usage Configurable
Sometimes you like to access a register directly, e.g. for speed or timing reasons. But in case of IO registers, you want to keep this register configurable somewhere in the code.
Often you find code like this:
#define LED_PORT PORTB // ... LED_PORT |= 0x01;
In many cases it is possible to replace such code with a simple reference:
constexpr auto &ledPort = PORTB; // ... ledPort |= 0x01;
The auto
keyword automatically uses the type of the variable defined as PORTB
, including the volatile
flag. This is working for most situations, but in some cases the register declarations are made in a such way that you still have to use macros. Yet, it is worth a try, for the sake of type safe code.
constexpr
is added to tell the compiler to directly reference PORTB
and do not create a new reference variable for it.
When to Use Macros
There are a few situations where you cannot avoid using macros. But if you are using macros, you should try to follow this rules:
- Use macros only in “cpp” files.
This will limit the scope of the macros, which will help you find any conflicts. You actually just have to search in one file for the problem. - Add a unique prefix to the name and only use uppercase letters.
A unique prefix in front of each macro reduces the risk of naming conflicts. At least you should prefix them with the name of your project, library or class. Better would be an unique identifier equal to your namespace (but uppercase). - Use only a macro if a C++ constant does not work.
Looking at code for e.g. the Arduino platform, there are a few uses for preprocessor macros:
- Remove debugging code from an application.
The C++ constants usually do not work here, because strings constants are kept, even they are not actually used. I am puzzled why, because there is an option to remove unused string literals, which is not set. - Use a constant for something which is already declared as macro.
People writing libraries to access MCU features seems to be obsessed with macros. Instead of declaring each register as a volatile variable, they clutter the whole namespace with tons of macro declarations.
See also: How and Why to Use Namespaces
Learn More

Make your Code Safe and Readable with Flags

C++ Templates for Embedded Code

Write Less Code using the “auto” Keyword

How and Why to use Namespaces

Event-Based Firmware (Part 2/2)

This is a good article and I am really glad that I read it.
Considering your awesome explanation, I would like to give you a suggestion. I think many people just use the typical libraries in Arduino which is full of Macros and they just do it the same. Some experts may have C compiler background and after seeing the typical Arduino libraries they may just stick to the old habits.
To be honest as a person who used C compiler before, I do did not find any source to show the differences as clear as you did. So, I think you are the right person to do a bid favor for the community.
My suggestion is that you come up with a full guide on how to write a library while utilizing C++ to the max. This is going to help both the new users and the experienced users to utilize C++ fully, because currently no other source can guide them through.
Anyways, I would like to thank you for this awesome article.
Great article! Coming from C# I’ve learnt to trust the compiler. When I see so much hideous Arduino code, I assumed these were just the limitations of C/C++, the IDe, or the Compiler. Nope. It turns out the limitation is with the authors of the code using ancient techniques. Thanks again. I now know I am not the insane one!
This article is really great! The best I ever read to write good Arduino Lib. It’s even better if you could share reproducible repo for each example and write how to inspect the machine code 🙂
Thanks a lot, I learn a lot from this article.
Thanks a lot for this great article!
I have a question regarding macros. I know stumble upon a wrapper to Python in C++, pyscience11. The wrapper wants to define a function
isfinite
which is defined as macro in the header/usr/include/math.h
probably someone somewhere includedmath.h
instead ofcmath
(which is also something I often fight against).Is there a way to avoid the preprocessor replace this macro other than
#undef isfinite
, which appears dangerous?See: https://luckyresistor.me/2018/08/31/how-to-deal-with-badly-written-code/
If you use
#undef
, you have to keep the order of#include
statements in mind. If you wrap the include of the library and place the#undef
there, most likely you avoid problems. Wrapping the library would be the best way to deal with this situation.