Skip to content
Educora
Intermediate20 min6 / 10

Pointers, references and memory

Learn about memory addresses, the `&` and `*` operators, `nullptr`, dynamic memory (`new`/`delete`) and `std::unique_ptr`, which manages it safely.

Check yourself
In this lesson you will learn
  • Explain what a pointer is, take an address with & and dereference with *
  • Compare pointers with references and check for nullptr
  • Explain how new and delete allocate dynamic memory and why they are risky
  • Apply the RAII principle and std::unique_ptr

You want to lend a friend a book, but it is very heavy. Instead of carrying it, you write the address on a slip of paper: “12 Nizami Street, shelf 3”. With the address, your friend can find the book alone. In C++ a pointer is exactly that slip of paper: it stores not the value itself, but its address in memory. Pointers give C++ speed and flexibility, but carelessness can be costly.

Addresses and pointers

A computer's memory consists of numbered cells, and every variable lives at some address. The & operator returns a variable's address. An address is usually printed in hexadecimal and may be different every time the program runs:

C++
#include <iostream>

int main() {
    int coins = 50;
    std::cout << "Value: " << coins << '\n';
    std::cout << "Address: " << &coins << '\n';
    return 0;
}
The second line shows an address such as 0x7ffc5a2b1c4c — it differs on every computer and every run.
Definition
Pointer

A variable that stores the memory address of another variable. int* p means “p is a pointer to an int”. The expression *p reaches the value stored at that address; this is called dereferencing.

C++
#include <iostream>

int main() {
    int coins = 50;
    int* ptr = &coins;

    std::cout << "coins = " << coins << '\n';
    std::cout << "*ptr = " << *ptr << '\n';

    *ptr = 75;
    std::cout << "coins = " << coins << '\n';
    std::cout << std::boolalpha << (ptr == &coins) << '\n';
    return 0;
}
Expected output
coins = 50
*ptr = 50
coins = 75
true
*ptr = 75 changes the value at the pointer's address, that is, coins itself.
SyntaxMeaning
&xthe address of x
int* pdeclares a pointer to an int
*pthe value at the address in p
nullptra pointer that points nowhere
p->nameshort for (*p).name

References and nullptr

In the previous lessons you met references (int& ref = a;) — a reference is a second name for a variable. Unlike a pointer, a reference must be bound to a variable when it is declared and can never be re-bound to another one. A pointer, on the other hand, can be pointed at a different address at any time, or point nowhere at all.

C++
#include <iostream>

int main() {
    int a = 10;
    int b = 20;
    int& ref = a;
    int* ptr = &a;

    ref += 5;
    std::cout << "a = " << a << '\n';

    ptr = &b;
    *ptr += 5;
    std::cout << "b = " << b << '\n';
    std::cout << "a = " << a << '\n';
    return 0;
}
Expected output
a = 15
b = 25
a = 15

A pointer that points nowhere is set to nullptr. Dereferencing nullptr (*p) is undefined behaviour and usually crashes the program. So if a pointer may be empty, check it before use:

C++
#include <iostream>

void printValue(const int* p) {
    if (p == nullptr) {
        std::cout << "No value\n";
        return;
    }
    std::cout << "Value: " << *p << '\n';
}

int main() {
    int level = 3;
    printValue(&level);
    printValue(nullptr);
    return 0;
}
Expected output
Value: 3
No value

Dynamic memory: new and delete

Ordinary local variables are created on the stack and removed automatically when the function ends. Sometimes, though, you need to allocate memory while the program runs, in whatever size is needed. The new operator reserves space on the heap and returns its address. This memory is not freed by itself: you must release it by hand with delete (or delete[] for an array).

C++
#include <iostream>

int main() {
    int* p = new int(42);
    std::cout << *p << '\n';
    delete p;
    p = nullptr;

    int size = 4;
    int* squares = new int[size];
    for (int i = 0; i < size; i++) {
        squares[i] = i * i;
    }
    std::cout << squares[3] << '\n';
    delete[] squares;
    return 0;
}
Expected output
42
9

RAII and smart pointers

Modern C++ solves these problems with the RAII principle (Resource Acquisition Is Initialization): an object takes ownership of a resource and gives it back automatically when the object is destroyed. std::vector and std::string work exactly like this. For a single object there is the smart pointer std::unique_ptr from <memory>: it is the sole owner of the memory and deletes it itself when it goes out of scope.

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

int main() {
    std::unique_ptr<int> score = std::make_unique<int>(100);
    *score += 50;
    std::cout << "Score: " << *score << '\n';

    std::unique_ptr<int> other = std::move(score);
    std::cout << std::boolalpha << (score == nullptr) << '\n';
    std::cout << "Other: " << *other << '\n';
    return 0;
}   // other deletes the memory automatically
Expected output
Score: 150
true
Other: 150
A unique_ptr cannot be copied; ownership can only be handed over with std::move, after which score is empty.

Key points

  • A pointer stores another variable's address: int* p = &x;.
  • & gives an address, and * reaches the value at an address.
  • A reference is always bound to one variable; a pointer can be re-pointed and can be nullptr.
  • Memory from new must be freed with delete (delete[] for arrays), or it leaks.
  • RAII: std::vector, std::string and std::unique_ptr free memory automatically — prefer them.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
What does the expression &x give?