2.09 Macros (#define) vs const vs constexpr: Flash ROM (.rodata) & Scope Safety
Executive Summary: Analyzing constants in C++. We contrast legacy C preprocessor macros (#define) with type-safe const and compile-time constexpr, explaining why macros lack scope and type verification, and how static constexpr constants are placed 100% in Flash ROM (.rodata) with 0 SRAM consumption.
💻 1. Annotated Source Code
#include <iostream> #include <string> using namespace std; int main() { const double MY_PI = 3.14159; const string MY_NAME = "John"; cout << MY_PI << endl; //MY_NAME = "Rob"; cout << MY_NAME << endl; return 0; }
📐 2. Architecture & UML Class Model
<<struct>>
ConstexprMemoryPool
.rodata Placement (Zero SRAM)
Attributes / Data Members
+PI : constexpr double = 3.141592653589793
+MAX_BUFFER_SIZE : constexpr size_t = 128
+DEVICE_ID : constexpr uint32_t = 0xAA5500FF
Operations / Methods
+calculateCircumference(radius: double) : constexpr double
📚 3. Core C++ Concepts Deep-Dive
1. Legacy #define Macros
#define BUFFER_SIZE 64 is a dumb text substitution performed by the preprocessor. It ignores scope, has no type verification, and is invisible to GDB symbolic debuggers.
2. const vs constexpr
const: Read-only variable; may be evaluated at runtime.constexpr: Guaranteed compile-time constant expression; evaluated by the compiler during build.
⚡ 4. Embedded Systems & Hardware Reality
1. Zero SRAM Overhead with constexpr
constexpr primitive values are embedded directly into assembly instructions as immediate operands (MOV R0, #64), consuming 0 bytes of SRAM and 0 memory read cycles.
💡 5. Production-Ready Embedded Refactoring
Replacing macros with type-safe constexpr definitions:
💡 Production-Ready Refactor
#include <cstdint> // BAD (Legacy C Macro): No type, no scope, debugger blind // #define MAX_VOLTAGE 3.3 // GOOD (Modern C++): Type-safe, scoped, compile-time verified namespace HardwareConfig { constexpr float MAX_VOLTAGE_V = 3.3f; constexpr uint32_t SPI_BAUD_RATE = 10'000'000; // 10 MHz constexpr uint8_t MAX_RETRY_COUNT = 3; }
📝 Knowledge Verification Quiz
Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.
Q1. Why is 'constexpr uint32_t MAX_SIZE = 128;' superior to '#define MAX_SIZE 128'?
Detailed Explanation:
constexpr offers strong type checking, respects namespace encapsulation, and produces debug symbols for GDB.
Q2. Where are 'static constexpr' lookup tables placed in the microcontroller memory map?
Detailed Explanation:
The linker places
static constexpr tables in the read-only data (.rodata) section in Flash ROM.
Q3. What happens if a macro '#define SQUARE(x) x * x' is called as 'SQUARE(2 + 3)'?
Detailed Explanation:
Macros perform raw text substitution; without parenthesis
(x) * (x), operator precedence produces 2 + 6 + 3 = 11.
Q4. Can a constexpr variable be modified after its definition?
Detailed Explanation:
constexpr guarantees immutability; values cannot be changed after definition.