Project 4.09 Section 4 ⚡ Embedded Relevance: Critical std::vector push_back Capacity vs Size Heap Reallocation Real-Time Jitter

4.09 Capacity vs Size, Geometric Growth Reallocation & Real-Time Heap Hazards

Executive Summary: Exploring dynamic arrays via std::vector. We analyze capacity vs size, geometric heap reallocation mechanics, pointer invalidation risks during push_back(), and why dynamic vectors are replaced by static bounded vectors in real-time firmware.

💻 1. Annotated Source Code

#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main() {
	vector<int> someVec;
	vector<string> anotherVec(3);

	someVec.push_back(1);
	someVec.push_back(2);
	someVec.push_back(3);

	cout << "someVec size: " << someVec.size() << endl;

	anotherVec[0] = "John";
	anotherVec[1] = "Bob";
	anotherVec[2] = "Sally";

	anotherVec.push_back("Shannon");

	for (int val : someVec) {
		cout << val << endl;
	}

	cout << endl;

	for (string name : anotherVec) {
		cout << name << endl;
	}

	cout << endl;
	cout << "Front and back of anotherVec:" << endl;
	cout << "front: " << anotherVec.front() << endl;
	cout << "back: " << anotherVec.back() << endl;

	anotherVec.pop_back();
	anotherVec.insert(anotherVec.begin(), "Don");

	cout << "\nAfter modification: " << endl;
	cout << "front: " << anotherVec.front() << endl;
	cout << "back: " << anotherVec.back() << endl;

	return 0;
}

📐 2. Architecture & UML Class Model

📐 Dynamic std::vector Heap Allocation vs etl::vector Model
+ Public - Private # Protected
<<class>> StdVectorModel Heap Dynamic Vector
-_M_start : T* (Heap Pointer)
-_M_finish : T* (End Element Pointer)
-_M_end_of_storage : T* (Capacity Pointer)
+push_back(val: const T&) : void
+capacity() : size_t const
+size() : size_t const
<<embedded-etl>> EtlVectorFixed Zero-Heap Alternative
+buffer[CAPACITY] : T
+currentSize : size_t
+push_back(val: const T&) : bool
+is_full() : bool const
🔗 Architectural Relationships & Hierarchy
StdVectorModel ─ ─ > refactors to in embedded ─ ─ > EtlVectorFixed

📚 3. Core C++ Concepts Deep-Dive

1. Size vs Capacity

  • Size: The number of active elements currently in the vector (.size()).
  • Capacity: The total number of elements allocated in heap memory before reallocation is needed (.capacity()).

2. Geometric Growth & Iterator Invalidation

When push_back() exceeds current capacity, std::vector allocates a new heap buffer (typically $1.5\times$ or $2\times$ larger), copies/moves all existing elements, and frees the old buffer. All existing pointers and iterators to elements are invalidated!

⚡ 4. Embedded Systems & Hardware Reality

1. Real-Time Latency Spikes during Vector Growth

A push_back() is usually $O(1)$ amortized, but when reallocation occurs, it spikes to $O(N)$ with dynamic heap allocation latency, causing missed real-time deadlines in motor control or audio processing loops.

2. Memory Fragmentation

Repeated vector expansion allocates and frees progressively larger blocks, causing severe heap fragmentation on constrained SRAM microcontrollers.

💡 5. Production-Ready Embedded Refactoring

Pre-reserving capacity or using fixed-capacity bounded vectors:

💡 Production-Ready Refactor
#include <vector>

// If std::vector MUST be used, reserve capacity upfront at boot
std::vector<int> createTelemetryVector(size_t expected_items) {
    std::vector<int> vec;
    vec.reserve(expected_items); // Allocates ONCE; eliminates dynamic reallocations
    return vec;
}

📝 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 occurs internally when push_back() is called on a std::vector whose size() equals its capacity()?
A A new larger heap buffer is allocated, all existing elements are copied/moved, the old buffer is deleted, and existing pointers/iterators are invalidated
B The newest element is silently dropped
C The vector throws a std::out_of_range exception
D The microcontroller restarts
Detailed Explanation: When capacity is exhausted, std::vector reallocates a larger memory block on the heap, moves existing elements, and frees the old block, invalidating all existing references/iterators.
Q2. What is the time complexity of vector::push_back() when a reallocation is triggered?
A O(N) linear time (proportional to element count)
B O(1) constant time
C O(log N) logarithmic time
D O(1/N) inverse time
Detailed Explanation: Reallocation requires allocating new memory and copying/moving all $N$ existing elements, taking $O(N)$ time.
Q3. How does vector::reserve(N) protect embedded applications from reallocation jitter?
A It pre-allocates heap memory for N elements upfront, guaranteeing zero reallocations for insertions up to size N
B It limits the vector to N bytes in ROM
C It converts the vector to a stack array
D It enables multithreading synchronization
Detailed Explanation: reserve() allocates the requested capacity in a single initial allocation, ensuring subsequent push_back() operations run in deterministic $O(1)$ time without reallocation.
Q4. Why is storing raw pointers to std::vector elements dangerous?
A Any subsequent push_back() that triggers a reallocation will invalidate the pointer, creating a hazardous dangling pointer
B Vectors encrypt pointer addresses
C Pointers cannot address heap memory
D Vectors delete elements after 1 second
Detailed Explanation: If a vector reallocates its internal buffer, existing elements move to a new memory address, leaving stored pointers pointing to freed memory (use-after-free bug).