Tutorial

C++ for Beginners Part 6: Pointers and Memory Management

Pointers are the most misunderstood part of C++ — and the key to understanding how memory actually works. This guide demystifies them from scratch: addresses, dereferencing, pointer arithmetic, dynamic allocation with new/delete, and how smart pointers make memory safe.


SERIES

C++ for Beginners: A Complete Guide

An eight-part beginner-friendly journey through C++: from writing your first program and understanding variables, through control flow, functions, and arrays, to the heart of C++ — pointers, classes, inheritance, and polymorphism. Each article is self-contained and builds on the previous, giving you both a quick reference and a progressive path to mastery.

Table of Contents

What is memory?

When your program runs, the operating system gives it a region of RAM. Every byte in that region has a unique address — a number like 0x7ffd3e8c.

Variables are stored at addresses:

C++
int age = 25;
// stored somewhere in RAM, e.g. at address 0x7ffd1234

A pointer is a variable that stores a memory address.


The address-of operator &

& retrieves the memory address of a variable:

C++
int age = 25;
std::cout << &age;   // something like 0x7ffd1234

Declaring a pointer

A pointer type is written as T* — "pointer to T":

C++
int  x   = 42;
int* ptr = &x;    // ptr holds the address of x

std::cout << ptr;    // address: 0x7ffd1234
std::cout << *ptr;   // value at that address: 42

The dereference operator *

*ptr means "go to the address stored in ptr and give me the value there":

C++
int  x   = 10;
int* ptr = &x;

*ptr = 99;              // change the value at the address
std::cout << x;         // 99 — we changed x through the pointer

Think of a pointer as a remote control: ptr is the remote, *ptr is pressing the button that actually changes the TV.


Null pointers

A pointer that doesn't point to anything should be set to nullptr:

C++
int* ptr = nullptr;

if (ptr == nullptr) {
    std::cout << "Pointer is null
";
}

// NEVER dereference a null pointer:
// *ptr = 5;   // crash: segmentation fault

Always check for nullptr before dereferencing a pointer you received from outside your code.


Pointers and arrays

Array names decay to a pointer to their first element:

C++
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;    // p points to arr[0]

std::cout << *p;       // 10  (arr[0])
std::cout << *(p+1);   // 20  (arr[1])
std::cout << *(p+2);   // 30  (arr[2])

This is pointer arithmetic — adding an integer to a pointer moves it forward by that many elements (not bytes). p + 1 is not address + 1; it's address + sizeof(int).


Pointers vs references

Pointer Reference
Syntax int* p int& r
Can be null Yes No (must bind to a valid object)
Can be reassigned Yes No (always refers to same object)
Needs * to access Yes No (transparent)

Use references when possible — they're safer. Use pointers when you need optional values (nullable) or when you're managing memory manually.


The stack vs the heap

C++ has two main memory regions:

The stack

  • Automatic, managed by the compiler
  • Variables created inside a function live here
  • Freed automatically when the function returns
  • Fixed size (~1–8 MB)
C++
void foo() {
    int x = 10;   // on the stack
}   // x is automatically destroyed here

The heap

  • Manual memory, managed by the programmer
  • Allocated with new, freed with delete
  • Large (limited by available RAM)
  • Lives until you explicitly free it

Dynamic memory: new and delete

Allocating a single value

C++
int* p = new int(42);   // allocate an int on the heap, initialize to 42
std::cout << *p;        // 42

delete p;               // free the memory
p = nullptr;            // good practice: null the pointer after delete

Allocating an array

C++
int size = 10;
int* arr = new int[size];   // heap array

for (int i = 0; i < size; i++) arr[i] = i * 2;

delete[] arr;   // note: delete[] for arrays, not delete
arr = nullptr;

Common memory errors

Memory leak — forgot to delete

C++
void leak() {
    int* p = new int(100);
    // forgot delete p;
    // 4 bytes never returned to the OS
}
// Each call to leak() permanently loses 4 bytes

Use after free

C++
int* p = new int(5);
delete p;
std::cout << *p;   // undefined behaviour — p points to freed memory

Double free

C++
int* p = new int(5);
delete p;
delete p;   // crash — freeing already-freed memory

Smart pointers (C++11) — the modern solution

Smart pointers are wrappers that automatically delete memory when it goes out of scope. They live in <memory>.

std::unique_ptr — exclusive ownership

C++
#include <memory>

std::unique_ptr<int> p = std::make_unique<int>(42);
std::cout << *p;   // 42

// p automatically calls delete when it goes out of scope — no manual delete needed

Only one unique_ptr can own a given object. You can move ownership but not copy it:

C++
auto p1 = std::make_unique<int>(10);
auto p2 = std::move(p1);   // p1 is now null, p2 owns the int

std::shared_ptr — shared ownership

C++
#include <memory>

auto sp1 = std::make_shared<int>(99);
auto sp2 = sp1;   // both sp1 and sp2 own the int

std::cout << sp1.use_count();   // 2
// Memory is freed when the last shared_ptr goes out of scope

When to use each

unique_ptr shared_ptr
Ownership One owner Multiple owners
Overhead Zero Reference counter
Default choice Yes Only when sharing

Rule of thumb: Default to unique_ptr. Use shared_ptr only when multiple objects genuinely need to share ownership.


Practical example: dynamic array

C++
#include <iostream>
#include <memory>

int main() {
    int size;
    std::cout << "How many numbers? ";
    std::cin >> size;

    auto arr = std::make_unique<int[]>(size);

    for (int i = 0; i < size; i++) {
        std::cout << "Enter number " << (i+1) << ": ";
        std::cin >> arr[i];
    }

    int sum = 0;
    for (int i = 0; i < size; i++) sum += arr[i];

    std::cout << "Sum: " << sum << "
";
    std::cout << "Average: " << (double)sum / size << "
";

    // arr is automatically deleted when it goes out of scope
}

Key takeaways

  • Every variable lives at a memory address; & gives you that address.
  • A pointer holds an address; * dereferences it to get the value.
  • Always initialize pointers (to nullptr if not yet assigned).
  • Heap memory (new) must be deleted — or you have a memory leak.
  • Use std::unique_ptr and std::shared_ptr instead of raw new/delete in modern C++.

Next up: object-oriented programming — classes, objects, and encapsulation.


Was this article helpful?

w

webencher Editorial

Software engineers and technical writers with 10+ years of combined experience in algorithms, systems design, and web development. Every article is reviewed for accuracy, depth, and practical applicability.

More by this author →