I\'m coming back to some C development after working in C++ for a while. I\'ve gotten it into my head that macros should be avoided when not necessary in favor of making the com
I've been working in embedded systems for over a dozen years and use C primarily. My comments are specific to this field. There are three ways to create constants that have specific implications for these types of applications.
1) #define: macros are resolved by the C preprocessor before the code is presented to the C compiler. When you look at headers files provided by processor vendors, they typically have thousands of macros defining access to the processor registers. You invoke a subset of them in your code and they become memory accesses in your C source code. The rest disappear and are not presented to the C compiler.
Values defined as macros become literals in C. As such, they do not result in any data storage. There is no data memory location associated with the definition.
Macros can be used in conditional compilation. If you want to strip out code based on feature configuration then you have to use macro definitions. For example:
#if HEARTBEAT_TIMER_MS > 0
StartHeartBeatTimer(HEARTBEAT_TIMER_MS);
#endif
2) Enumerations: Like macro definitions, enumerations do not result in data storage. They become literals. Unlike macro definitions, they are not stripped by the preprocessor. They are C language constructs and will appear in preprocessed source code. They cannot be used to strip code via conditional compilation. They cannot be tested for existence at compile time or runtime. Values can only be involved in runtime conditionals as literals.
Unreferenced enumerations won't exist at all in compiled code. On the other hand, compilers may provide warnings if enumerated values are not handled in a switch statement. If the purpose of the constant is to produce a value that must be handled logically then only an enumeration can provide the degree of safety that comes with the use of switch statements.
Enumerations also have an auto-increment feature, so if the purpose of the constant is to be used as an constant index into an array then I would always go with an enumeration to avoid unused slots. In fact, the enumeration itself can produce a constant representing a number of items that can be used in an array declaration.
Since enumerations are C language constructs, they are definitely evaluated at compiler time. For example:
#define CONFIG_BIT_POS 0
#define CONFIG_BIT_MASK (1 << CONFIG_BIT_POS)
CONFIG_BIT_MASK is a text substitute for (1 << CONFIG_BIT_POS). When (1 << CONFIG_BIT_POS) is presented to the C compiler, it may or may not produce the literal 1.
enum {
CONFIG_BIT_POS = 0,
CONFIG_BIT_MASK = (1 << CONFIG_BIT_POS)
};
In this case CONFIG_BIT_MASK is evaluated and becomes the literal value 1.
Finally, I would add that macro definitions can be combined to produce other code symbols, but cannot be used to create other macro definitions. That means that if the constant name must be derived then it can only be an enumeration created by a combination of macro symbols or macro expansion, such as with list macros (X macros).
3) const: This is a C language construct that makes a data value read only. In embedded applications this has an important role when applied to static or global data: it moves the data from RAM into ROM (typically, flash). (It does not have this effect on locals or auto variables because they are created on the stack or in registers at runtime.) C compilers can optimize it away, but certainly this can be prevented, so aside from this caveat, const data actually takes up storage in read only memory at runtime. That means that it has type, which defines that storage at a known location. It can be the argument of sizeof(). It can be read at runtime by an external application or a debugger.
These comments are targeted at embedded applications. Obviously, with a desktop application, everything is in RAM and much of this doesn't really apply. In that context, const makes more sense.