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:

#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.

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:

#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 like this:

#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:

#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.

#include "Example.h"
void Example::doSomething() {
  // ...
  mcuSpecificPart();
  // ...
}

Now you create additional implementation files for the different MCUs you would like to support:

#include "Example.h"
void Example:: mcuSpecificPart() {
  // ...
}
#include "Example.h"
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:

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 many 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.

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