8.07 Managing Fixed Arrays of Heap Objects vs Intrusive Lists & Bitmask Memory Pools
💻 1. Annotated Source Code
#include <iostream> #include "Exhibit.h" using namespace std; int main() { const int EXHIBIT_COUNT = 3; Exhibit* exhibitPtrs[EXHIBIT_COUNT]; exhibitPtrs[0] = new Exhibit("T-Rex Skeleton", 101, 350.5); exhibitPtrs[1] = new Exhibit("Ancient Egypt", 204, 480.25); exhibitPtrs[2] = new Exhibit("Space Exploration", 309, 290.75); for (int i = 0; i < EXHIBIT_COUNT; i++) { cout << "Exhibit: " << exhibitPtrs[i]->getName() << endl; cout << "\tRoom: " << exhibitPtrs[i]->getRoomNumber() << endl; cout << "\tDisplay Size (sq ft): " << exhibitPtrs[i]->getDisplaySize() << endl; cout << endl; }//end for for (int i = 0; i < EXHIBIT_COUNT; i++) { delete exhibitPtrs[i]; exhibitPtrs[i] = nullptr; } return 0; }
#ifndef EXHIBIT_H #define EXHIBIT_H #include <string> using namespace std; class Exhibit { public: Exhibit(string name, int roomNumber, double displaySize); string getName() const; int getRoomNumber() const; double getDisplaySize() const; private: string name; int roomNumber; double displaySize; }; #endif
#include "Exhibit.h" Exhibit::Exhibit(string name, int roomNumber, double displaySize) { this->name = name; this->roomNumber = roomNumber; this->displaySize = displaySize; } string Exhibit::getName() const { return name; } int Exhibit::getRoomNumber() const { return roomNumber; } double Exhibit::getDisplaySize() const { return displaySize; }
📐 2. Architecture & UML Class Model
📚 3. Core C++ Concepts Deep-Dive
1. Fixed Arrays of Pointers
A static array of pointers (Exhibit* exhibits[3]) holds pointers to individually allocated heap objects. While the array container is statically sized on the stack or in BSS, the individual objects reside on the dynamic heap.
2. Multi-Object Deallocation Lifecycle
To prevent memory leaks, every element in the pointer array must be explicitly deleted and reset to nullptr when its lifecycle ends.
⚡ 4. Embedded Systems & Hardware Reality
1. Out-of-Order Deallocation Fragmentation
If exhibits (or network packets, sensor readings) are allocated and freed in random order, general-purpose heap allocators create fragmented memory holes that cannot be coalesced, eventually causing allocation failure.
2. Fixed-Block Allocator with Bitmask Free-List
A Fixed-Block Allocator pre-allocates $N$ memory blocks of identical size in static SRAM. A single integer bitmask tracks which blocks are free. Allocation and deallocation are $O(1)$ constant time operations with zero fragmentation.
💡 5. Production-Ready Embedded Refactoring
Here is a deterministic, zero-fragmentation Fixed-Block Pool Allocator using a bitmask:
#include <cstdint> #include <cstddef> #include <new> template <typename T, size_t BlockCount = 32> class FixedBlockPool { static_assert(BlockCount <= 32, "Bitmask supports up to 32 blocks"); alignas(alignof(T)) std::byte memory_[BlockCount][sizeof(T)]; uint32_t allocation_mask_{0}; // Bit = 1 (Used), Bit = 0 (Free) public: template <typename... Args> T* allocate(Args&&... args) noexcept { for (size_t i = 0; i < BlockCount; ++i) { if (!(allocation_mask_ & (1UL << i))) { allocation_mask_ |= (1UL << i); // Mark block as used return new (memory_[i]) T(std::forward<Args>(args)...); } } return nullptr; // Out of blocks } void free(T* ptr) noexcept { if (!ptr) return; for (size_t i = 0; i < BlockCount; ++i) { if (reinterpret_cast<T*>(memory_[i]) == ptr) { ptr->~T(); allocation_mask_ &= ~(1UL << i); // Clear bitmask return; } } } };
📝 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.
uint32_t, 4 bytes) has 32 individual bits, where each bit represents the used/free state of one block.
delete frees the target memory but does not modify the pointer variable itself. Failing to set nullptr causes null checks to pass incorrectly, resulting in use-after-free bugs.