EDIT: microtherion gives an excellent answer which corrects some of my points here, particularly about memory usage.
As you've identified, there are certain situations where you're forced to use a #define
, because the compiler won't allow a const
variable. Similarly, in some situations you're forced to use variables, such as when you need an array of values (i.e. you can't have an array of #define
).
However, there are many other situations where there isn't necessarily a single 'correct' answer. Here are some guidelines which I would follow:
Type safety
From a general programming point-of-view, const
variables are usually preferable (where possible). The main reason for that is type-safety.
A #define
(preprocessor macro) directly copies the literal value into each location in code, making every usage independent. This can hypothetically result in ambiguities, because the type may end up being resolved differently depending on how/where it's used.
A const
variable is only ever one type, which is determined by its declaration, and resolved during initialisation. It will often require an explicit cast before it will behave differently (although there are various situations where it can safely be implicitly type-promoted). At the very least, the compiler can (if configured correctly) emit a more reliable warning when a type issue occurs.
A possible workaround for this is to include an explicit cast or a type-suffix within a #define
. For example:
#define THE_ANSWER (int8_t)42
#define NOT_QUITE_PI 3.14f
That approach can potentially cause syntax problems in some cases though, depending on how it's used.
Memory usage
Unlike general purpose computing, memory is obviously at a premium when dealing with something like an Arduino. Using a const
variable vs. a #define
can affect where the data is stored in memory, which may force you to use one or the other.
const
variables will (usually) be stored in SRAM, along with all other variables.
- Literal values used in
#define
will often be stored in program space (Flash memory), alongside the sketch itself.
(Note that there are various things which can affect exactly how and where something is stored, such as compiler configuration and optimisation.)
SRAM and Flash have different limitations (e.g. 2 KB and 32 KB respectively for the Uno). For some applications, it's quite easy to run out of SRAM, so it can be helpful to shift some things into Flash. The reverse is also possible, although probably less common.
PROGMEM
It's possible to get the benefits of type-safety while also storing the data in program space (Flash). This is done using the PROGMEM
keyword. It doesn't work for all types, but it's commonly used for arrays of integers or strings.
The general form given in the documentation is as follows:
dataType variableName[] PROGMEM = {dataInt0, dataInt1, dataInt3...};
String tables are a bit more complicated, but the documentation has full details.
#define
is the obvious choice. My example is in naming Analog pins - like A5. There is no appropriate type for it that could be used as aconst
so the only choice is to use a#define
and let the compiler substitute it as text input before interpreting the meaning.