Project 8.06 Section 8 ⚡ Embedded Relevance: High Double Indirection Pointer to Pointer Cache Locality Pointer Chasing AoS vs SoA

8.06 Pointer-to-Pointer (Drone**) Fleet Arrays vs Flat Contiguous Array-of-Structures (AoS)

Executive Summary: Analyzing dynamic fleet management using double pointer indirection (Drone**). We contrast pointer-to-pointer architectures with contiguous flat memory buffers, showing how pointer chasing destroys CPU cache performance and wastes precious RAM on microcontrollers.

💻 1. Annotated Source Code

#include <iostream>
#include "Drone.h"
using namespace std;

void printFleet(Drone** fleet, int size);

int main() {
	int numDrones = 0;
	string model;
	double battery = 0.0;

	cout << "How many drones are in your fleet?  ";
	cin >> numDrones;

	Drone** droneFleet = new Drone* [numDrones];

	for (int i = 0; i < numDrones; i++) {
		cout << "Enter model name for drone " << (i + 1) << ": ";
		cin.get();
		getline(cin, model);

		cout << "Enter battery life (as a percentage) for " << model << ":  ";
		cin >> battery;

		droneFleet[i] = new Drone(model, battery);
	} // end of the for loop

	cout << "\nDrone Fleet Summary:" << endl;
	printFleet(droneFleet, numDrones);

	// cleanup
	for (int i = 0; i < numDrones; i++) {
		delete droneFleet[i];
		droneFleet[i] = nullptr;
	}

	delete[] droneFleet;


	return 0;
}


void printFleet(Drone** fleet, int size) {
	for (int i = 0; i < size; i++) {
		cout << "Drone " << (i + 1) << ": " << fleet[i]->getModelName()
			<< " | Battery: " << fleet[i]->getBatteryLife() << "%" << endl;
	}
}
#ifndef DRONE_H
#define DRONE_H

#include <string>
using namespace std;

class Drone {
	public:
		Drone(string modelName, double batteryLife);
		string getModelName() const;
		double getBatteryLife() const;

	private:
		string modelName;
		double batteryLife;
};

#endif
#include "Drone.h"

Drone::Drone(string modelName, double batteryLife) {
	this->modelName = modelName;
	this->batteryLife = batteryLife;
}

string Drone::getModelName() const {
	return modelName;
}

double Drone::getBatteryLife() const {
	return batteryLife;
}

📐 2. Architecture & UML Class Model

📐 Drone Class & Fleet Dynamic Manager Model
+ Public - Private # Protected
<<class>> Drone Telemetry Device
-droneId : int32_t
-batteryLevel : float
-altitudeMeters : float
+Drone(id: int, battery: float)
+takeOff(targetAlt: float) : void
+land() : void
+getAltitude() : float const
+getBattery() : float const
<<class>> DroneFleet Fleet Manager
-drones : Drone** (Dynamic Pointer Array)
-fleetSize : size_t
+DroneFleet(size: size_t)
+~DroneFleet()[frees all drones]
+getDrone(idx: size_t) : Drone*
🔗 Architectural Relationships & Hierarchy
DroneFleet ◆── manages array of drone pointers ◆── Drone

📚 3. Core C++ Concepts Deep-Dive

1. Pointer-to-Pointer Mechanics (Type**)

A pointer-to-pointer stores the memory address of another pointer. In classical C++, dynamic 2D tables or arrays of polymorphic objects are created by allocating an array of pointers (Drone** fleet = new Drone*[N]) and then individually allocating each object (fleet[i] = new Drone(...)).

2. Double Allocation Overhead

Managing $N$ objects requires $N + 1$ distinct heap allocations ($1$ array of pointers $+ N$ individual objects) and $N + 1$ corresponding delete calls.

⚡ 4. Embedded Systems & Hardware Reality

1. The Hardware Cost of Pointer Chasing

Accessing fleet[i]->getBatteryLife() requires double dereferencing:

  1. Read the pointer from the pointer array: ptr = *(fleet + i).
  2. Dereference ptr to read the object in distant heap memory: ptr->battery.

Because each object was allocated separately on the heap, they are scattered randomly across SRAM. On modern pipelined microcontrollers (ARM Cortex-M7 with L1 data cache), this causes frequent cache misses and memory stall cycles.

2. Flat Contiguous Storage: Array of Structures (AoS)

Storing objects contiguously in a single flat array (std::array<Drone, MaxDrones>) requires zero pointer storage (saving $4 \times N$ bytes of RAM) and maximizes CPU spatial cache locality.

💡 5. Production-Ready Embedded Refactoring

Here is an embedded fleet manager using flat contiguous memory with zero pointer indirection:

💡 Production-Ready Refactor
#include <cstdint>
#include <string_view>
#include <array>

struct DroneTelemetry {
    char     model_name[16];
    uint16_t battery_milli_volts; // e.g. 3700 mV
    uint8_t  active_status;
};

template <size_t MaxFleetSize>
class FlatDroneFleet {
private:
    // Contiguous in SRAM; 100% spatial cache locality; 0 pointer overhead
    std::array<DroneTelemetry, MaxFleetSize> drones_{};
    size_t active_count_{0};

public:
    bool register_drone(std::string_view model, uint16_t battery_mv) noexcept {
        if (active_count_ >= MaxFleetSize) return false;
        
        DroneTelemetry& d = drones_[active_count_++];
        size_t len = model.size() < 15 ? model.size() : 15;
        for (size_t i = 0; i < len; ++i) d.model_name[i] = model[i];
        d.model_name[len] = '\0';
        d.battery_milli_volts = battery_mv;
        d.active_status = 1;
        return true;
    }

    const DroneTelemetry* data() const noexcept { return drones_.data(); }
    size_t count() const noexcept { return active_count_; }
};

📝 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 'pointer chasing' and why does it degrade execution speed on pipelined microcontrollers?
A Following multiple levels of pointer indirection to scattered memory locations, causing CPU pipeline stalls and cache misses
B A compiler optimization that speeds up loops
C A tool for debugging memory leaks
D A technique to compress pointer tables in ROM
Detailed Explanation: Pointer chasing forces the CPU to wait for memory loads before it can determine the next address to fetch. When data is scattered across SRAM, cache misses stall the pipeline.
Q2. How much RAM is wasted storing a Drone** array of 50 pointers on a 32-bit ARM Cortex-M microcontroller?
A 200 bytes (50 pointers * 4 bytes per pointer) plus heap allocator bookkeeping headers
B 0 bytes
C 1024 bytes
D 4 bytes
Detailed Explanation: On a 32-bit MCU, each pointer requires 4 bytes. 50 pointers consume 200 bytes just for address storage, plus 8-16 bytes of heap allocator metadata per object.
Q3. Why is a contiguous flat array (std::array<Drone, N>) superior to an array of pointers (Drone*[])?
A It guarantees sequential memory layout for optimal spatial cache locality, eliminates pointer storage overhead, and requires zero heap allocations
B It allows drones to fly faster
C It converts the class to pure virtual methods
D It automatically generates unit tests
Detailed Explanation: Flat contiguous arrays place objects sequentially in memory, maximizing CPU cache line hits, eliminating pointer overhead, and removing dynamic memory risks.
Q4. What happens if an individual Drone object in a Drone** array is deleted, but the pointer in the array is not set to nullptr?
A The array retains a dangling pointer; iterating over the array and calling methods on it causes undefined behavior
B The compiler automatically re-allocates the drone
C The pointer becomes a null reference safely
D The CPU hardware halts with a power-saving mode
Detailed Explanation: The array element still points to the freed memory. If loop code attempts to read that array element, a use-after-free bug occurs.