Skip to main content
PProfileMatch

C++ Pointers and References Explained

ProgrammingLesson 5 of 65 min read

What you'll learn

  • Explain what a pointer stores and use & and * correctly
  • Compare pointers with references and know when to use each
  • Read the common const pointer forms
  • Recognise dangling pointers, leaks and null dereferences, and know that smart pointers help

On this page

  1. Addresses and pointers
  2. References: a safer alias
  3. const with pointers
  4. Dynamic memory and smart pointers
  5. Common mistakes
  6. How this shows up in interviews and real projects

Pointers have a fearsome reputation, but the idea is small: every variable lives at an address in memory, and a pointer is a variable that stores such an address. Once that clicks, a large part of C++ stops looking like magic. This lesson builds the idea step by step and then covers the bugs that give pointers their reputation.

Addresses and pointers

The address-of operator & gives the memory address of a variable. A pointer type adds a star: int* means "pointer to an int". The dereference operator * goes the other way and means "the thing this pointer points to". The same symbols appear in two roles: in a declaration the star describes the type, and in an expression it dereferences.

pointer_basics.cpp
#include <iostream>
using namespace std;

int main() {
    int balance = 1000;
    int* ptr = &balance;

    cout << balance << endl;
    cout << *ptr << endl;
    *ptr = 1500;
    cout << balance << endl;
    cout << boolalpha << (ptr == &balance) << endl;

    int* nothing = nullptr;
    if (nothing == nullptr) {
        cout << "nothing points nowhere" << endl;
    }
    return 0;
}

Output

1000
1000
1500
true
nothing points nowhere

Writing through *ptr changed balance because they refer to the same memory. If you print ptr itself you get an address that differs from run to run, so we compare it rather than print it. Use nullptr (C++11 and later) for a pointer that points to nothing, instead of the old NULL or 0. Always initialise pointers: either to a valid address or to nullptr. An uninitialised pointer holds garbage, and dereferencing it is undefined behavior.

References: a safer alias

A reference is another name for an existing variable. You write int& alias = score; and from then on alias and score are the same object. A reference must be initialised when declared, cannot be reseated to refer to something else later, and cannot be null. Those restrictions are why references are safer, and why they are the preferred way to pass arguments in modern C++ (see C++ Functions).

FeaturePointer T*Reference T&
Can be nullYes (nullptr)No
Can be reassigned to another objectYesNo, it is bound once
Must be initialised at declarationNo, but you shouldYes
Access the target*ptr or ptr->memberUse it like the original variable
Typical useOptional values, dynamic memory, data structuresFunction parameters, aliases
ref_vs_ptr.cpp
#include <iostream>
using namespace std;

void applyDiscountRef(double& price) {
    price *= 0.9;
}

void applyDiscountPtr(double* price) {
    if (price == nullptr) {
        return;
    }
    *price *= 0.9;
}

int main() {
    int score = 80;
    int& alias = score;
    alias = 95;
    cout << score << endl;

    double price = 200;
    applyDiscountRef(price);
    cout << price << endl;
    applyDiscountPtr(&price);
    cout << price << endl;
    applyDiscountPtr(nullptr);
    cout << "safe with null" << endl;
    return 0;
}

Output

95
180
162
safe with null

The reference version needs no checks and no & at the call site, whereas the pointer version must test for nullptr and be called with &price. Use a reference when the argument is required, and a pointer when "no object" is a valid answer.

const with pointers

Where const appears relative to the star changes its meaning. A handy way to read it is right to left.

DeclarationRead asCan change the pointed-to value?Can repoint the pointer?
const int* ppointer to a const intNoYes
int* const pconst pointer to an intYesNo
const int* const pconst pointer to a const intNoNo

Dynamic memory and smart pointers

Local variables live on the stack and vanish when their function ends. To create something that outlives the function, you allocate it on the heap with new and later release it with delete (or delete[] for arrays allocated with new[]). Forgetting to release is a memory leak, and releasing twice is undefined behavior. Modern C++ avoids both with smart pointers from <memory>. A std::unique_ptr owns an object exclusively and deletes it automatically when it goes out of scope, and std::shared_ptr allows shared ownership with reference counting.

heap_and_smart.cpp
#include <iostream>
#include <memory>
#include <string>
using namespace std;

struct Account {
    string owner;
    double balance;
};

int main() {
    int* scores = new int[3]{10, 20, 30};
    cout << scores[1] << " " << *(scores + 2) << endl;
    delete[] scores;
    scores = nullptr;

    auto acc = make_unique<Account>(Account{"Rohan", 1000});
    acc->balance += 250;
    cout << acc->owner << " has " << acc->balance << endl;
    return 0;
}

Output

20 30
Rohan has 1250

scores[1] and *(scores + 1) mean the same thing, because adding 1 to a pointer moves it forward by one element, not one byte. The -> operator is shorthand for dereferencing and then accessing a member, so acc->balance equals (*acc).balance. The raw array needed a manual delete[], while acc cleans up after itself when main ends. A unique_ptr cannot be copied, only moved, since two owners would delete the same object.

Common mistakes

  • Dereferencing a null or uninitialised pointer. The program typically crashes with a segmentation fault (Segmentation fault (core dumped) on Linux). The behavior is undefined, so never rely on any specific result. Check ptr != nullptr first.
  • Dangling pointers. After delete p; the pointer still holds the old address. Using it is undefined behavior. Set it to nullptr after deleting, or better, use a smart pointer.
  • Memory leaks. new without a matching delete leaks memory for the life of the program. Tools like Valgrind or the -fsanitize=address compiler flag can detect this.
  • Mismatched delete. Memory from new[] must be freed with delete[], not delete. Mixing them is undefined behavior.
  • Returning the address of a local variable. int* f() { int x = 5; return &x; } returns a pointer to something that is destroyed on return. GCC warns address of local variable 'x' returned.
  • Copying a unique_ptr. auto b = a; fails with error: use of deleted function mentioning unique_ptr. Move it with std::move(a) instead.

How this shows up in interviews and real projects

Pointer questions come up very often in C++ interviews: pointer versus reference, what a dangling pointer is, how new and delete differ from malloc and free, what unique_ptr and shared_ptr do, and the rule of ownership. Linked lists and trees, standard interview data structures, are built from pointers. If you are targeting large-company interviews, how to crack FAANG interviews covers how this kind of knowledge is assessed. Next, use all of it to build your own types in C++ Classes, Objects and std::vector.

Try it yourself

Write a function incrementAll(int* arr, int size) that adds 1 to every element of an array through the pointer, and a second function maxOf(const int& a, const int& b) that returns the larger value. Test both with a small array in main, and check for nullptr in the first function.

Show a hint

Loop from 0 to size - 1 and use arr[i]++. Return early if arr == nullptr.

Key takeaways

  • A pointer stores an address: &x gets it and *p follows it. Initialise every pointer, using nullptr for none.
  • A reference is a non-null alias bound once at declaration; prefer it for function parameters.
  • Read const with pointers right to left: const int* protects the value, int* const protects the pointer.
  • Prefer std::unique_ptr and containers over raw new and delete to avoid leaks and dangling pointers.
  • Dereferencing null, using a deleted pointer or returning the address of a local are all undefined behavior.

Test yourself

A trick from this lesson's ride: & means where it lives; * means go there and look inside. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

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

A pointer stores a memory address, can be null and can be changed to point elsewhere, but must be dereferenced with *. A reference is an alias for an existing variable, cannot be null and cannot be rebound after initialisation. Prefer references for function parameters and pointers when a value can be absent.

What is nullptr in C++?

nullptr is the keyword for a null pointer, introduced in C++11. It has its own type that converts to any pointer type, which avoids the ambiguity of using 0 or NULL. Compare a pointer with nullptr before dereferencing it.

What are smart pointers in C++ and why use them?

Smart pointers such as std::unique_ptr and std::shared_ptr are wrapper classes in <memory> that automatically delete the object they manage when no longer needed. They prevent most memory leaks and dangling pointers that come from manual new and delete.