LESSON 07 · BEGINNER4 min read

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.

C++20 / C++23Free to readModule 2 of 11
YOUR NEXT SMALL WINS

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.

example.js⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

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.

notes.txtPlain text
+--------------------+------------------+------------------+-----------------------+| 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.

LET IT CLICK

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.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

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.

01

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.

your_turn_1.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
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.

02

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];?

your_turn_2.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
// 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.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What is array-to-pointer decay in C++?
02How much additional memory overhead does std::array<int, 10> introduce compared to a raw int arr[10]?
03Which operator must be used to deallocate an array allocated with int* data = new int[100];?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

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.

EXPECTED OUTPUT
Build completed. Binary executed.
[Exit code: 0]
main.cppC++
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

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.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.