Stack Arrays, Array Decay, and Dynamic Heap Allocation
Master C-style raw arrays, the array-to-pointer decay phenomenon, dynamic memory allocation with new/delete and new[]/delete[], and modern std::array alternatives.
By the end, you’ll be able to…
- Analyze array-to-pointer decay and explain why sizeof(array) differs inside vs outside function calls
- Implement manual dynamic memory allocation using operator new[] and delete[] without memory leaks
- Contrast C-style raw arrays with zero-overhead modern std::array and std::vector containers
01Stack Arrays and the Array-to-Pointer Decay Phenomenon
In C and C++, a raw stack array declared as int buffer[10]; represents a contiguous block of memory allocating exactly 10 * sizeof(int) = 40 bytes directly on the active stack frame. In the scope where it is declared, sizeof(buffer) evaluates to 40 bytes.
However, whenever a raw array is passed into a function or used in an expression expecting a pointer, it undergoes Array-to-Pointer Decay: the array silently degrades into a raw pointer to its first element (int*). Inside the function, sizeof(buffer) reports 8 bytes (the pointer size on 64-bit systems), and all compile-time bounds and size information are permanently lost!
Because decayed pointers do not carry size metadata, functions accepting raw arrays must require an explicit size parameter, creating fertile ground for buffer overflows and out-of-bounds memory vulnerabilities.
02Dynamic Allocation: new[] vs delete[]
When array dimensions are dynamic or known only at runtime, stack arrays cannot be used (variable-length arrays or VLAs are non-standard in C++). Developers must allocate memory dynamically on the heap using operator new[].
Crucially, allocating an array with new T[size] requires deallocating it with delete[] ptr, NOT delete ptr. Using plain delete on an array causes undefined behavior: it calls the destructor for only the first element and corrupts allocator book-keeping cookies.
When new T[n] is invoked, modern heap allocators store an internal prefix cookie immediately preceding the returned pointer recording the count of elements n. The delete[] operator reads this prefix to execute the destructor exactly n times before returning the memory chunk to the heap.
03Modern Alternatives: std::array vs std::vector
Modern C++ systems code deprecates raw C-style arrays in favor of two STL containers:
std::array<T, N>: A stack-allocated fixed-size container that wraps a raw C-array with zero runtime memory overhead. Unlike C arrays, std::array never decays to a pointer implicitly, supports copy assignment, and provides size() and at() bounds-checking.
std::vector<T>: A dynamic heap-allocated contiguous buffer that handles automated resizing, exponential capacity doubling, and deterministic RAII cleanup.
04Array Comparison Matrix
A comparison of array representations in modern C++ systems programming:
Standard systems engineering practice mandates std::array for compile-time fixed buffers and std::vector for runtime dynamic collections.
+--------------------+------------------+------------------+-----------------------+| Container Type | Memory Location | Decays to Ptr? | Bounds Checking |+--------------------+------------------+------------------+-----------------------+| T arr[N] (C-array) | Stack / Global | YES (Silent) | None (Unsafe) || std::array<T, N> | Stack / Embedded | NO (Type safe) | Optional (.at()) || T* ptr = new T[N] | Heap | Already a ptr | None (Manual delete[])|| std::vector<T> | Heap (RAII) | NO (Type safe) | Optional (.at()) |+--------------------+------------------+------------------+-----------------------+05Production Systems Architecture & Hardware Trade-Offs
From an operating system and hardware architecture perspective, systems software must account for physical execution realities that compiler abstractions intentionally hide. At the hardware level, instruction caches (I-Cache) and data caches (D-Cache) operate independently, and CPU pipeline front-ends rely heavily on branch target buffers (BTB) to minimize instruction fetch stalls. When code is organized into coherent, spatially contiguous blocks within translation units, binary code density increases, improving instruction cache hit rates across high-frequency execution loops.
Furthermore, modern linkers such as LLVM lld and mold perform Link-Time Optimization (LTO) across translation units. When LTO is enabled (-flto), the compiler writes LLVM bitcode into object files rather than final machine code, allowing the linker to perform cross-translation-unit function inlining, devirtualization, and dead-code stripping across the entire binary.
The ideas worth keeping.
- Raw C-arrays decay silently into pointers upon function invocation, discarding size metadata.
- Always pair new[] with delete[] to ensure all element destructors run and allocator cookies are respected.
- Prefer std::array for fixed-size stack buffers and std::vector for dynamic heap collections.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Exercise 1: Write a function template that accepts a refe
Write a function template that accepts a reference to a C-style array and returns its exact element count at compile time without decaying to a pointer.
Explore a worked solution
template <typename T, size_t N>[[nodiscard]] constexpr size_t array_size(T (&)[N]) noexcept { return N;}Accepting the array by reference T (&)[N] prevents array-to-pointer decay.
Exercise 2: What is the consequence of calling delete ptr
What is the consequence of calling delete ptr instead of delete[] ptr on an array allocated via int* ptr = new int[50];?
Explore a worked solution
// Calling delete instead of delete[] invokes undefined behavior.// For non-trivial types, the destructor is executed only for ptr[0], leaking resources for remaining elements.// Furthermore, the allocator metadata cookie (which stores array length) is misread,// leading to heap metadata corruption and subsequent allocator crashes.Recall how delete[] knows how many destructors to run.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Exercise 1: Write a function template that accepts a refe
Write a function template that accepts a reference to a C-style array and returns its exact element count at compile time without decaying to a pointer.
Build completed. Binary executed. [Exit code: 0]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.