Skip to content

Memory Management with Pointers ​

Chapter 12

Learning Outcomes ​

After studying this lecture, students should be able to:

  1. Identify and differentiate memory regions (static, stack, heap).
  2. Use references/aliases correctly in C++.
  3. Explain call stack mechanics and stack frames.
  4. Recognize stack overflow conditions.
  5. Manage dynamic memory with new/delete and understand fragmentation.
  6. Compare C++ memory handling with Java’s garbage collection.
  7. Use pointers safely; identify common memory errors.
  8. Perform pointer arithmetic and relate pointers to arrays.
  9. Describe garbage collection; understand smart pointers in C++.
  10. Master terminology: dangling pointers, garbage, memory leaks.

References & Aliases ​

A Review of C++ References & Aliases ​

  1. In a parameter declaration, the & symbol makes a reference.
    The parameter refers to the memory location of the original variable.

    cpp
    void limit(double& value, double max)
    {
        if (value > max)
        {
        value = max;
        }
    }
    
    double score = 120.5;
    limit(score, 100.0);
    std::cout << score << '\n'; // 100.
  2. In a variable declaration, & makes that variable an alias.

    cpp
    double score = 205.3;
    double &alias = score;

    The variable is a new name for the old variable location.

  3. Before an existing variable, & evaluates to the variable’s memory address.

    cpp
    double score = 91.3;
    std::cout << &score; // score's address.

Three Memory Regions (for Data Storage) ​

  • Static storage: storage for objects with static storage duration is allocated before program execution and remains for the program's lifetime.

  • Call Stack (or execution stack): memory associated with active functions.

    • Structured as stack frames (i.e., activation records)
  • Heap: dynamically allocated storage, managed by the runtime allocator.

Memory layout for each running process.
Memory layout for each running process.

Static Data Memory ​

  • The simplest memory to manage.

  • Includes objects with static (unchanging) storage duration. For example:

    • global variables,

    • static variables, and

    • machine code (instructions for function and class definitions).

  • Characteristics:

    • Storage requirements are known before execution.

    • The size of the static storage area is constant throughout execution.

The Call Stack ​

  • The Call Stack (i.e., the Runtime Stack or Execution Stack) is a contiguous memory region that grows and shrinks.

  • Supports function calls.

  • The stack grows when a function is called (activated); its stack frame (or activation record) is pushed on top.

  • It shrinks when the function terminates; storage is deallocated.

Stack Frames ​

  • For each function call, a stack frame stores local variables, parameters, and return linkage.

  • Its contents and time of allocation are determined at runtime. Its exact size and layout are known at compile time.

  • How is variable lifetime affected by stack management techniques?

Click here to step through this example code.

int add(int a, int b)
{
  return a + b;  // <- 3
}

int doubleAdd(int a, int b)
{
  return add(a, b)
    + add(b, a);  // <- 2
}

int main()
{
  int total = 0;
  total = add(1, 2);
  total = doubleAdd(3, 4); // <- 1

  return 0;
}
Example call stack for the above code.
Call Stack
main()
total → 0
doubleAdd()
int a → 3
int b → 4
add()
int a → 4
int b → 3
→

Stack Overflow ​

  • The call stack and heap grow towards each other (in many implementations) as required by program events.

  • The following relation must hold:

  • In other words, if the stack top bumps into the heap, or if the beginning of the heap is greater than the end, there are problems!

Heap Memory ​

  • Heap objects are dynamically allocated/deallocated at runtime (not associated with function call/return).

  • Dynamic storage is acquired at runtime, independently of function calls and returns.

  • The kind of data found on the heap is language-dependent.

    • Typically holds strings, dynamic arrays, objects, and linked structures

    • Java and C/C++ have different policies.

Heap Memory Example

Click here to step through this example code.

#include <iostream>

int main()
{
  const int SIZE = 3;
  int stackArray[SIZE];      // Declared on the stack
  int *heapArray;            // Declare pointer to memory location
  heapArray = new int[SIZE]; // Declare array on heap

  std::cout << "stackArray  = " << stackArray << std::endl;
  std::cout << "heapArray   = " << heapArray << std::endl;

  stackArray[2] = 20;
  heapArray[2] = 20;

  delete[] heapArray; // Free up the memory from the heap array
  heapArray = nullptr; // Set pointer to point to address 0.

  return 0;
}
  • The new operator allocates heap storage.

  • The delete or delete[] operators deallocate heap storage for reuse.

  • Space is allocated in variable-sized blocks, so deallocation may leave “holes” in the heap (fragmentation).

    • Compared to the deallocation of stack storage

Heap Management ​

  • Some languages (e.g., C, C++) leave heap storage deallocation to the programmer.

  • Others (e.g., Java, Perl, Python, list-processing languages) employ garbage collection to reclaim unused heap space.

  • new returns the start address of an unused block from the heap and changes its state from unused to reserved (and undefined).

  • Suppose a Point class has three 4-byte data members: , , .

  • Point firstCoord = new Point();
    requires at least allocated bytes.

Heap Allocation Failure ​

  • An allocation can fail when the program cannot obtain enough storage. By default, new throws std::bad_alloc exception.

  • Fragmentation can contribute to failure for requests that require a large contiguous block, such as an array allocation.

Heap Management Operations ​

  • delete releases a storage block to the allocator.

  • The storage block's status is set to unused and is available for allocation by future calls to new.

  • Failing to release unused storage can eventually contribute to heap allocation failure.

Pointers ​

  • Pointers are addresses (i.e., the value of a pointer variable is an address).

  • A pointer can point to an object with static, automatic (stack), dynamic (heap), or thread storage duration.

  • Java does not expose explicit pointer arithmetic. In typical JVM implementations, objects are heap allocated while local reference variables are stored in stack frames.

  • This topic is covered well in the textbook’s Chapter 12.

Memory Allocation Java vs C++ ​

JavaStack ExampleHeap Example
Primitivesint a = 3;int[] a = new int[1];
ClassesDoes not exist.String str = new String("Hi!");
C++Stack ExampleHeap Example
Primitivesint a = 3;int *a = new int;
Classesstring str("Hi!");string *str = new string("Hi!");

Examples in Code ​

In class, we will create and test out some more pointers.

Java Versus C/C++ Arrays ​

  • In Java, arrays are always allocated dynamically from heap memory.

  • In many other languages, including C++:

    StructureLocation
    Globally defined arraysstatic memory.
    Local (to a function) arraysstack storage.
    Dynamically allocated arraysheap storage.
  • Dynamically allocated arrays also have stack storage — a reference (pointer) to the heap block that holds the array.

Comparing Pointers ​

cpp
int i = 5, j = 5;
int *ptrJ1 = &j;
int *ptrJ2 = &j;
int *ptrI = &i;

True/False:

  • if (ptrJ1 == ptrJ2) {} → True
  • if (ptrJ1 == ptrI) {} → False
  • if (&ptrJ1 == &ptrJ2) {} → False
  • if (*ptrJ1 == *ptrI) {} → True

Click here to see the visualization for this code.

Assigning a value to a dereferenced pointer. ​

A pointer must point to a valid object before you can dereference it (follow the pointer).

cpp
int *p;
*p = 3;

Undefined Behavior! p holds an indeterminate location.

cpp
int foo;
int *p = &foo;
*p = 3;

This is fine. p points to foo.

Pointers to Pointers ​

DeclarationVisualization
int *x = &num;
double *y;
int **z = &x;

An Example

cpp
int num = 5;
int *pNum = &num;
int **ppNum = &pNum;

Then:

  • ppNum stores the memory location of pNum.

  • *ppNum stores the memory location of num.

  • **ppNum equals 5.

Array Pointers ​

Declaring Arrays ​

  • Typical C/C++ array declarations.

    cpp
    int arr[5];               // stack
    double arr1[10][15];      // stack
    int *intPtr = new int[5]; // heap
  • Typical Java array declarations:

    java
    int[] arr = new int[5];
    double[][] arr1 = new double[10][5];
    Object[] arr2 = new Object[100];

Allocation of Stack and Heap Space for an Array ​

Memory allocated on the stack and heap for int *arr = new int[10];
Memory allocated on the stack and heap for int *arr = new int[10];

Pointers and Arrays ​

  • An array identifier is not a pointer, but in most expressions it converts ("decays") to a pointer to its first element.

  • You can use the [] operator with pointers!

  • Example: int A[5];

    • Creates a memory block of 5 integers on the stack (5 × 4 bytes).
    • A (the pointer) points at the beginning of the array. A → A[0]. (A == &A[0])
      Array of 5 elements.Array of 5 elements.
  • Example:

    cpp
    int *x;
    int a[5] {-1, -2, -3, -4, -5};
    x = &a[2]; // x is the address of a[2]

    Pointer to 3 element in array.Pointer to 3 element in array.

    cpp
    for (int i = 0; i < 3; i++)
      x[i]++; // x[i] is the same as a[i+2]

    Pointer to 3 element in array.Pointer to 3 element in array.

Pointer Arithmetic ​

  • Integer arithmetic (+, -, ++, --, +=, -=) can be used with pointers that refer to elements of the same array (or one past its end).

  • Increment moves a pointer to the next element, scaled by the size of the pointed-to type.

    cpp
    int a[5] {-1, -2, -3, -4, -5};
    int *ptr = a;

    Pointer Arithmetic Example with an Array.Pointer Arithmetic Example with an Array.

    cpp
    *(ptr + 3) = 400;

    Pointer Arithmetic Example with an Array.Pointer Arithmetic Example with an Array.

Memory Errors ​

Pointer Pitfalls ​

Assigning values to uninitialized, null, or deleted pointers:

cpp
int* p;
*p = 3;
cpp
int* p = nullptr;
*p = 4;
cpp
int* p = new int;
delete p;
*p = 5;

Each of the above statements has undefined behavior and may result in a segmentation fault.

Memory Leaks ​

A memory leak occurs when dynamically allocated storage becomes unreachable without first being deallocated.

Example:

cpp
int *p = new int; // An int value's address
p = nullptr; // The allocated int is now unreachable.
  • Result?

  • Memory Leak! (Orphan Blocks)
    Must free memory block before changing reference.

  • A memory leak can diminish the performance of the computer by reducing the amount of available memory.

  • Eventually, in the worst case, too much of the available memory may become allocated

  • and all or part of the system or device stops working correctly, the application fails, or the system slows down.

TIP

Use Valgrind with --leak-check=yes option if your implementation has memory leaks.
Reference the Valgrind User Manual, The Valgrind Quick Start Guide, or Graphical User Interfaces.

* The -g flag includes debug information, making Valgrind reports easier to interpret.

Problem: Multiple Pointers to the Same Address ​

  • A second problem can occur when multiple pointers are assigned to a block of heap memory.

  • Deleting the block does not change any pointer automatically. All pointers to that block must be set to nullptr or they are dangling.

  • Dereferencing dangling pointers is undefined behavior and the allocator reuses the storage.

Dangling Pointer: A pointer that points to an invalid location. ​

Example:

cpp
int *p, *q;  // Create two pointers
p = new int; // Allocate an int on the heap.
q = p;       // Points to the same address.
*p = 4;      // Heap-allocated int set to 4.
delete q;    // Frees the heap memory.
*p = 3;      // Undefined behavior!
  • p and q point to the same location.

  • When q is deleted, p becomes a dangling pointer!

Garbage Collection ​

Memory Leaks and Garbage Collection ​

The popularity of object-oriented programming has led to an emphasis on heap storage management.
Objects are considered active or inactive.

  • Active objects: blocks accessible through a pointer or reference.

  • Inactive objects: inaccessible blocks; no reference exists (also called orphans or garbage).

The terms accessible and inaccessible may be more figurative than objectively true.

Garbage Collection ​

  • A garbage collector identifies unreachable objects and reclaims their storage.

  • Some garbage collectors also compact the heap by moving objects together to reduce fragmentation.

  • C and C++ normally require the programmer to manage dynamic storage. If dynamically allocated storage is not explicitly freed, it can become garbage and cause a memory leak.

    • Non-standard garbage collectors are available for C++.
  • Java, C#, Python, JavaScript, Perl, and most scripting languages perform garbage collection.

    • Some still use new or a similar construct to create objects, but they perform automatic reclamation.
  • Garbage collection was pioneered by languages like Lisp, which constantly creates and destroys linked lists.

  • C++ includes smart pointers, enabling automatic object lifetime management.

Review ​

  • Three commonly discussed memory regions:

    • Static storage
    • Call stack
    • Heap
  • Problems with heap storage:

    • Memory leaks (garbage): failure to free storage when pointers (references) are reassigned.

    • Dangling pointers: when storage is freed, but references to the storage still exist.

Allocation of Stack and Heap Space for an Array ​

Memory allocated on the stack and heap for int *arr = new int[10];
Memory allocated on the stack and heap for int *arr = new int[10];

Garbage ​

  • Garbage: dynamically allocated storage that is no longer reachable by the program but has not been reclaimed.

  • Garbage is created in several ways:

    • A function ends while the only pointer to dynamically allocated storage is a local variable. The pointer is gone.

    • A node is unlinked from a linked data structure but is not deallocated.

    • …

Terminology ​

  • A dangling pointer (or dangling reference) is a pointer (reference) that refers to an object whose lifetime has ended.

  • An orphan (or garbage) is a dynamically allocated memory block that is no longer accessible through any pointer.

  • A memory leak is a gradual loss of available memory due to the creation of garbage.

Review Questions ​

The following are some questions for you to verify your understanding.

Conceptual Questions ​

  1. What are the three primary memory regions in a C++ program, and what are their main characteristics?

  2. Define the following terms:

    • Dangling pointer
    • Memory leak
    • Garbage (or orphaned memory)
  3. Explain the difference between stack allocation and heap allocation.

  4. How does a stack overflow occur, and what typically triggers it?

  5. Compare memory management in C++ and Java. What are the key differences regarding allocation and deallocation?

Pointer and Reference Questions ​

  1. What is the difference between a pointer and a reference in C++?

  2. Given the code snippet below, what is the output and why?

    cpp
    int x {5};
    int* p = &x;
    *p = 10;
    std::cout << x;
  3. Identify the potential error(s) in the following code:

    cpp
    int* ptr = new int{42};
    delete ptr;
    std::cout << *ptr;
  4. Explain how pointer arithmetic works when traversing an array. Give a short example.

Applied / Problem-Solving Questions ​

  1. A function allocates memory on the heap but never frees it. What problem does this create over time? How can it be avoided?

  2. Draw a diagram of the stack after the following function calls (before they return):

    cpp
    void foo() { int a; }
    void bar() { int b; foo(); }
    int main() { bar(); return 0; }
  3. Consider the following C++ snippet:

    cpp
    int* arr {new int[5]};
    arr[0] = 10;
    delete[] arr;
    arr[0] = 20;

    What is wrong with this code?
    How would you fix it?

Lab 3: Pointers ​

Complete Lab 3 on Blackboard.