Project 8.02 Section 8 ⚡ Embedded Relevance: Critical const ROM-ability .rodata Pointer to Const Const Pointer MISRA C++

8.02 Pointer to Const vs Const Pointer & Placing Lookups in Microcontroller Flash ROM

Executive Summary: Mastering the four permutations of const with pointers: mutable pointer to mutable data, pointer to const data, const pointer to mutable data, and const pointer to const data. We analyze how const enables ROM-ability, placing lookup tables and calibration data into Flash ROM (.rodata) to save precious SRAM.

💻 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

📐 The 4 Pointer Constness Permutations & Flash ROM Model
+ Public - Private # Protected
<<compilation-unit>> ConstPointerMatrix Constness Enforcer
+ptrToNonConst : int* (Mutable Pointer, Mutable Data)
+ptrToConst : const int* (Mutable Pointer, Read-Only Data in Flash)
+constPtrToNonConst : int* const (Fixed Pointer, Mutable Data in RAM)
+constPtrToConst : const int* const (Fixed Pointer, Read-Only Data in Flash)
+testMutations() : void

📚 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 (or int 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:

💡 Production-Ready Refactor
#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.

Q1. What is the meaning of 'const uint8_t* const ptr'?
A A const (immutable) pointer pointing to const (read-only) data
B A pointer that can be reassigned to any memory address
C A mutable pointer to mutable uint8_t data
D An array of 8-bit integers on the heap
Detailed Explanation: Reading right-to-left: ptr is a const pointer (its stored address cannot change) to a const uint8_t (the pointed-to data cannot be modified).
Q2. Why is const-correctness vital for conserving SRAM in embedded firmware?
A Data marked const/constexpr is placed by the linker in Flash ROM (.rodata), consuming 0 bytes of SRAM
B It compresses variables using ZIP encoding in RAM
C It makes the CPU run at double clock frequency
D It automatically deletes unused variables during runtime
Detailed Explanation: Linkers place immutable (const / constexpr) data into Flash memory (.rodata), freeing SRAM for dynamic stack variables and buffers.
Q3. Which pointer declaration correctly models a permanent hardware peripheral base address whose registers change asynchronously?
A volatile PeripheralRegs* const PERIPHERAL_BASE
B const PeripheralRegs* PERIPHERAL_BASE
C PeripheralRegs* volatile PERIPHERAL_BASE
D const volatile PeripheralRegs* PERIPHERAL_BASE
Detailed Explanation: volatile PeripheralRegs* const specifies a const pointer (the base memory address is permanent) pointing to volatile hardware registers (the register values change in hardware).
Q4. What happens if code attempts to write to a variable placed in Flash ROM (.rodata)?
A A hardware MemManage Fault or BusFault occurs because Flash memory is read-only at runtime
B The Flash controller automatically updates the sector in 1 clock cycle
C The write succeeds without errors
D The CPU ignores the write and resets the stack pointer
Detailed Explanation: Flash memory cannot be written like RAM at runtime without unlocking the flash controller; writes trigger a hardware fault (BusFault / MemManage Fault).