8.02 Pointer to Const vs Const Pointer & Placing Lookups in Microcontroller Flash ROM
💻 1. Annotated Source Code
#include <iostream> using namespace std; void noChange(const double* const someValue); void noConst(); void cp2ncd(); void ncp2cd(); void cp2cd(); int main() { double* myDubz = new double(5.25); noConst(); cout << endl; cp2ncd(); cout << endl; ncp2cd(); cout << endl; cp2cd(); cout << endl; noChange(myDubz); delete myDubz; myDubz = nullptr; return 0; } void noChange(const double* const someValue) { cout << *someValue << endl; } //1. non-const pointer to non-const data void noConst() { cout << "In noConst" << endl; int* intPtr = new int(50); cout << "\toriginal value:" << *intPtr << endl; *intPtr = 100; cout << "\tchanged data: " << *intPtr << endl; delete intPtr; intPtr = new int(125); cout << "\tnew int entirely: " << *intPtr << endl; delete intPtr; } // 2. const pointer to non-const data void cp2ncd() { cout << "In cp2ncd" << endl; int* const intPtr = new int(100); cout << "\toriginal value: " << *intPtr << endl; *intPtr = 250; cout << "\tmodified value: " << *intPtr << endl; // intPtr = new int(222); delete intPtr; } // 3. non-const pointer to const data void ncp2cd() { cout << "In ncp2cd" << endl; const int* intPtr = new int(500); cout << "\toriginal value: " << *intPtr << endl; // *intPtr = 600; delete intPtr; intPtr = new int(1000); //pointer can change! cout << "\tnew value: " << *intPtr << endl; delete intPtr; } // 4. const pointer to const data void cp2cd() { cout << "In cp2cd" << endl; const int* const intPtr = new int(5000); cout << "\toriginal value: " << *intPtr << endl; // *intPtr = 6000; // can't do this, the data is constant // intPtr = new int(6000); //can't do this, the pointer is constant delete intPtr; }
📐 2. Architecture & UML Class Model
📚 3. Core C++ Concepts Deep-Dive
1. The Four Permutations of Pointer Constness
int* ptr: Mutable pointer to mutable data (can reassign pointer, can mutate value).const int* ptr(orint const* ptr): Pointer to const data (cannot mutate data via pointer; can reassign pointer).int* const ptr: Const pointer to mutable data (can mutate data; cannot reassign pointer).const int* const ptr: Const pointer to const data (immutable address, immutable data).
2. Read-Right-to-Left Rule
To decipher complex pointer declarations, read from right to left: const int* const ptr $\rightarrow$ "ptr is a const pointer to a const int".
⚡ 4. Embedded Systems & Hardware Reality
1. ROM-ability and the .rodata Section
In microcontrollers with limited SRAM (e.g. 16KB-64KB) and larger Flash (e.g. 128KB-1MB), saving RAM is critical. When data structures, calibration maps, and strings are declared const or constexpr, the linker places them in the .rodata section in Flash ROM.
2. Hardware Peripheral Base Address Safety
Pointers to hardware peripheral register blocks must be declared as const pointers to volatile data (volatile RegisterMap* const). This ensures the pointer permanently addresses the peripheral and cannot be accidentally redirected.
💡 5. Production-Ready Embedded Refactoring
Here is how const correctness is applied in production hardware peripheral drivers:
#include <cstdint> // Hardware register map structure struct UartHardwareMap { volatile uint32_t SR; // Status Register volatile uint32_t DR; // Data Register volatile uint32_t BRR; // Baud Rate Register volatile uint32_t CR1; // Control Register 1 }; // 1. Const pointer to volatile hardware register (Permanent MMIO base address) UartHardwareMap* const UART1_HW = reinterpret_cast<UartHardwareMap* const>(0x40011000); // 2. Calibration curve stored 100% in Flash ROM (.rodata section) struct AdcCalibrationCurve { const uint16_t raw_counts[5]; const float voltage_volts[5]; }; static constexpr AdcCalibrationCurve SENSOR_CALIB = { .raw_counts = {0, 1024, 2048, 3072, 4095}, .voltage_volts = {0.0f, 0.825f, 1.65f, 2.475f, 3.3f} };
📝 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.
ptr is a const pointer (its stored address cannot change) to a const uint8_t (the pointed-to data cannot be modified).
const / constexpr) data into Flash memory (.rodata), freeing SRAM for dynamic stack variables and buffers.
volatile PeripheralRegs* const specifies a const pointer (the base memory address is permanent) pointing to volatile hardware registers (the register values change in hardware).