@@ -4,6 +4,40 @@ Patterns for safe, efficient memory management in embedded C++.
44
55---
66
7+ ## Stack First — Heap as Fallback
8+
9+ ** The fundamental rule** : Prefer stack allocation. Use heap only when stack
10+ won't work.
11+
12+ ### Why stack first?
13+
14+ * ** Deterministic** : No allocation failures, no fragmentation
15+ * ** Fast** : No allocator overhead, just stack pointer adjustment
16+ * ** Safe** : Automatic cleanup when scope exits
17+ * ** Predictable** : Memory usage known at compile time
18+
19+ ### When heap is acceptable
20+
21+ Use heap ** only** when:
22+
23+ 1 . ** Size unknown at compile time** (truly dynamic data)
24+ 2 . ** Object outlives current scope** (ownership transfer)
25+ 3 . ** Polymorphism required** (virtual dispatch needs pointer)
26+ 4 . ** Stack would overflow** (very large buffers, >1KB typical threshold)
27+
28+ ``` cpp
29+ // STACK FIRST — Default choice
30+ std::array<uint8_t , 256 > buffer; // Fixed size, stack
31+ CommandParser parser; // Value type, stack
32+ RGB color{255, 128, 64}; // Small struct, stack
33+
34+ // HEAP ONLY WHEN NECESSARY
35+ std::unique_ptr<LightMode > mode; // Polymorphic, needs heap
36+ std::vector<uint8_t> dynamicData(size); // Runtime size, needs heap
37+ ```
38+
39+ ---
40+
741## RAII (Resource Acquisition Is Initialization)
842
943Wrap resources in objects whose destructors clean up automatically:
@@ -86,61 +120,115 @@ public:
86120
87121---
88122
89- ## Stack vs Heap
123+ ## Stack Allocation Patterns
90124
91- ### Prefer stack allocation for small, fixed-size data
125+ ### Default to stack for all fixed-size data
92126
93127```cpp
94- // GOOD — Stack allocated, no heap overhead
128+ // GOOD — Stack allocated, deterministic
95129void processPacket() {
96130 std::array<uint8_t, 64> buffer;
131+ PacketHeader header;
97132 // Use buffer...
98- } // Automatically cleaned up
133+ } // Automatic cleanup, no fragmentation
99134
100- // AVOID — Unnecessary heap allocation
135+ // BAD — Unnecessary heap allocation
101136void processPacket() {
102137 auto buffer = std::make_unique<std::array<uint8_t, 64>>();
103- // ...
138+ auto header = std::make_unique<PacketHeader>();
139+ // Why heap? Fixed size, doesn't outlive scope
104140}
105141```
106142
107- ### Use heap for dynamic or large allocations
143+ ### Stack-based class members
108144
109145``` cpp
110- // OK — Size determined at runtime
111- std::unique_ptr<uint8_t []> createBuffer (size_t size) {
112- return std::make_unique<uint8_t[ ] >(size);
146+ // GOOD — Members on stack (inside object)
147+ class CommandProcessor {
148+ private:
149+ std::array<uint8_t, 256> m_buffer; // Stack when object is stack
150+ CommandParser m_parser; // Value member, not pointer
151+ size_t m_bufferLen = 0;
152+ };
153+
154+ // Usage — entire object on stack
155+ void handleCommand() {
156+ CommandProcessor processor; // All members stack-allocated
157+ processor.process(data);
113158}
159+ ```
160+
161+ ---
162+
163+ ## When Heap Is Necessary
164+
165+ ### Polymorphism requires indirection
166+
167+ ```cpp
168+ // Heap needed — runtime type selection
169+ class LightController {
170+ private:
171+ std::unique_ptr<LightMode> m_currentMode; // Could be Sunrise, Candle, etc.
114172
115- // OK — Large buffer that would overflow stack
173+ public:
174+ void setMode(ModeType type) {
175+ // Must use heap for polymorphic ownership
176+ m_currentMode = createMode(type);
177+ }
178+ };
179+ ```
180+
181+ ### Ownership transfer across scopes
182+
183+ ``` cpp
184+ // Heap needed — object outlives creating function
185+ std::unique_ptr<LightMode> createMode (ModeType type) {
186+ return std::make_unique<OwllarkSunrise >(); // Caller owns result
187+ }
188+ ```
189+
190+ ### Truly dynamic size
191+
192+ ```cpp
193+ // Heap needed — size unknown at compile time
194+ void receiveData(size_t len) {
195+ std::vector<uint8_t> buffer(len); // Runtime size requires heap
196+ // ...
197+ }
198+ ```
199+
200+ ### Very large buffers
201+
202+ ``` cpp
203+ // Heap acceptable — would overflow typical 4-8KB task stack
116204auto largeBuffer = std::make_unique<std::array<uint8_t , 8192 >>();
117205```
118206
119207---
120208
121209## Containers
122210
123- ### Use `std::vector` for dynamic arrays
211+ ### Prefer ` std::array ` (stack) over ` std::vector ` (heap)
124212
125213``` cpp
126- // BAD — Raw array with manual management
127- uint8_t* data = new uint8_t[count] ;
128- // ... use data
129- delete[] data;
130-
131- // GOOD — Vector manages memory
132- std::vector<uint8_t> data(count);
133- // ... use data
134- // Automatic cleanup
214+ // BEST — Fixed size known at compile time → std::array (stack)
215+ std::array< uint8_t , 64 > buffer ;
216+ std::array<LightCommand, 10 > commandHistory;
217+
218+ // ACCEPTABLE — Size truly dynamic → std::vector (heap)
219+ std::vector< uint8_t > data (runtimeSize);
220+
221+ // BAD — Using vector when size is fixed
222+ std::vector<uint8_t> buffer(64); // Why not std::array?
135223```
136224
137- ### Use ` std::array ` for fixed-size arrays
225+ ### Avoid C-style arrays
138226
139227```cpp
140- // BAD — C-style array, no bounds checking
228+ // BAD — C-style array, no bounds checking, decays to pointer
141229uint8_t buffer[64];
142230
143- // GOOD — std::array with bounds checking
231+ // GOOD — std::array with bounds checking, doesn't decay
144232std::array<uint8_t, 64> buffer;
145233buffer.at(i); // Throws on out-of-bounds
146234```
@@ -217,12 +305,12 @@ std::shared_ptr<Config> sharedConfig;
217305
218306## Memory-Constrained Patterns
219307
220- ### Preallocate buffers
308+ ### Preallocate fixed buffers as members
221309
222310``` cpp
223311class CommandProcessor {
224312private:
225- std::array<uint8_t, 256> m_buffer; // Preallocated
313+ std::array<uint8_t, 256> m_buffer; // Preallocated, no runtime allocation
226314 size_t m_bufferLen = 0;
227315
228316public:
@@ -235,29 +323,67 @@ public:
235323};
236324```
237325
238- ### Use placement new for memory pools (advanced)
326+ ### Fixed-capacity collections
239327
240328```cpp
241- // Preallocated memory pool for frequently created objects
242- alignas(Command) std::array<uint8_t, sizeof(Command) * 10> pool;
243- size_t poolIndex = 0;
244-
245- Command* allocateCommand() {
246- void* mem = &pool[poolIndex * sizeof(Command)];
247- poolIndex++;
248- return new(mem) Command();
249- }
329+ // Instead of std::vector that grows dynamically
330+ template<typename T, size_t Capacity>
331+ class FixedVector {
332+ private:
333+ std::array<T, Capacity> m_data;
334+ size_t m_size = 0;
335+
336+ public:
337+ bool push_back(const T& value) {
338+ if (m_size >= Capacity) return false;
339+ m_data[m_size++] = value;
340+ return true;
341+ }
342+ // ...
343+ };
344+
345+ // Usage — all stack, no heap fragmentation
346+ FixedVector<LightCommand, 10> commandQueue;
347+ ```
348+
349+ ### Object pools on stack (advanced)
350+
351+ ``` cpp
352+ // Preallocated pool avoids repeated allocation/deallocation
353+ template <typename T, size_t Size>
354+ class ObjectPool {
355+ private:
356+ std::array<T, Size> m_objects;
357+ std::array<bool, Size> m_inUse{};
358+
359+ public:
360+ T* acquire() {
361+ for (size_t i = 0; i < Size; i++) {
362+ if (!m_inUse[ i] ) {
363+ m_inUse[ i] = true;
364+ return &m_objects[ i] ;
365+ }
366+ }
367+ return nullptr;
368+ }
369+
370+ void release(T* obj) {
371+ size_t idx = obj - m_objects.data();
372+ if (idx < Size) m_inUse[idx] = false;
373+ }
374+ };
250375```
251376
252377---
253378
254379## Key Takeaways
255380
256- * ** Always use RAII** — resources cleaned up automatically
257- * ** Prefer ` std::unique_ptr ` ** for owned heap objects
258- * ** Use references** for non-owning dependencies
259- * ** Stack-allocate** small, fixed-size data
260- * ** Never use raw ` new ` /` delete ` ** in application code
381+ * ** Stack first** — heap is a fallback, not the default
382+ * ** ` std::array ` over ` std::vector ` ** when size is known
383+ * ** Heap only when necessary** — polymorphism, ownership transfer, dynamic size
384+ * ** RAII always** — resources cleaned up automatically
385+ * ** ` std::unique_ptr ` ** when heap is required
386+ * ** Never raw ` new ` /` delete ` ** in application code
261387
262388## Related Best Practices
263389
0 commit comments