Language[lang]

Pointers in C++: what they are and how they work

Section
Language [lang]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
7 min read
In this answer6 clauses
  1. 1How do & and * work in C++?
  2. 2What is nullptr in C++?
  3. 3How do pointers and arrays work?
  4. 4Pointer vs reference in C++: which should you use?
  5. 5Do you need new and delete in C++?
  6. 6Common pointer mistakes in C++

Abstract

Pointers in C++ are variables that hold the address of another variable: int* p = &score; makes p point at score, and *p reads or changes score through it. A pointer that points at nothing holds nullptr; test it with if (p) before following it, because dereferencing a null pointer is undefined behavior. Use a reference when there is always an object, a pointer when there may be none, and std::unique_ptr instead of a bare new and delete.

A long band of memory cells. At left, a small outlined box with a blue dot inside; a blue arrow arcs from the dot over four empty cells and lands on a larger hatched box at right.
Figure 1 — A pointer is a value that says where another one is.

Pointers in C++ are variables that hold the address of another variable, the place in memory where it is stored. int* p = &score; makes p point at score: & takes the address, and *p reads or changes the value stored there. A pointer that points at nothing should hold nullptr, and must not be followed with *.

As a whole program:

[Example 1:
#include <iostream>

int main() {
    int score = 90;
    int* p = &score;  // p holds the address of score

    std::cout << *p << '\n';     // prints 90

    *p = 95;                     // changes score through p
    std::cout << score << '\n';  // prints 95
}

— end example]

It printed 90, then 95: the assignment through p changed score itself.

The samples are C++17, built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and all four printed the same output for every sample. Since When lists the first compiler releases that support each standard’s features.

How do & and * work in C++?

&score is the address of score, and *p is the variable whose address p holds. The star does two jobs: in a declaration, int* p says “p is a pointer to an int”; in an expression, *p follows the pointer to that int, which is called dereferencing it. Read *p and you get the value; assign to *p and the variable changes.

Two boxes of memory: score holds 90, with a made-up address, 1000, written under it; p, a wider pointer box, holds 1000, with an arrow to score. Below: p holds 1000, following p gives 90, the address of score is 1000.
Figure 2 — A pointer holds an address (1000 here is a made-up example); the star follows it to the value stored there.

The 1000 is made up: a real address differs from run to run and machine to machine, which is why no sample prints one. Two pointers can hold the same address, and a pointer can be pointed at another variable later:

[Example 2:
std::cout << std::boolalpha;

int score = 90;
int* p = &score;
int* q = &score;
std::cout << (p == q) << '\n';  // true: the same address

*q = 95;                        // change score through q
std::cout << *p << '\n';        // 95: p sees it too

int best = 100;
p = &best;                      // p now points at best
std::cout << *p << '\n';        // 100
std::cout << (p == q) << '\n';  // false
std::cout << score << '\n';     // 95: score did not change

— end example]

All four printed true, 95, 100, false and 95. A write through either pointer changes score, because both lead to it; p = &best; moves the pointer and leaves score alone.

What is nullptr in C++?

nullptr is the value a pointer holds when it points at nothing. It is the pointer literal, added in C++11, and it converts to any pointer type. Old code writes NULL or 0 instead; write nullptr, which has a type of its own, std::nullptr_t, and unlike 0 and NULL still works as a null pointer after a template has passed it on.

Test a pointer before you follow it: if (p) is false for a null pointer, and dereferencing a null pointer is undefined behavior, meaning the standard sets no rule for what happens: the program may crash, or may run on with wrong results. A function that searches can return nullptr for “not found”. Here find returns a const int*, a pointer through which the int can be read but not changed:

[Example 3:
const int* find(const std::vector<int>& scores, int wanted) {
    for (const int& s : scores) {
        if (s == wanted) {
            return &s;  // the address of the match
        }
    }
    return nullptr;     // not found
}

int main() {
    int* p = nullptr;
    if (p) {
        std::cout << *p << '\n';
    } else {
        std::cout << "p points at nothing\n";
    }

    std::cout << std::boolalpha;

    std::vector<int> scores{90, 75, 82, 68};

    const int* hit = find(scores, 82);
    if (hit) {
        std::cout << *hit << '\n';           // 82
    }

    const int* miss = find(scores, 100);
    std::cout << (miss == nullptr) << '\n';  // true
}

— end example]

It printed p points at nothing, 82 and true. The caller tests hit before it reads *hit, so a miss is never dereferenced.

How do pointers and arrays work?

An array’s name turns into a pointer to its first element wherever a pointer is expected, so int* p = scores; points at scores[0]. From there, p + 1 points at the next element, and p[2] means *(p + 2):

[Example 4:
std::cout << std::boolalpha;

int scores[4] = {90, 75, 82, 68};
int* p = scores;  // the name becomes a pointer to scores[0]

std::cout << (p == &scores[0]) << '\n';  // true
std::cout << *p << '\n';                 // 90
std::cout << *(p + 1) << '\n';           // 75
std::cout << p[2] << '\n';               // 82: p[2] is *(p + 2)

for (int* q = scores; q != scores + 4; ++q) {
    std::cout << *q << '\n';  // 90, 75, 82, 68
}

— end example]

It printed true, 90, 75 and 82, then the loop printed the four scores in order. Adding to a pointer is defined only inside the array and one step past its end ([expr.add]): scores + 4 may be formed, as the loop’s stopping point, but not dereferenced, and anything further out is undefined behavior. C++ array covers declaring arrays and getting their length.

Pointer vs reference in C++: which should you use?

Use a reference when there is always an object to refer to, and a pointer when “nothing” is a possible answer or the target has to change. A reference is declared with &: in int& score, the ampersand means “reference to”, an alias for an existing int, not “address of”.

Question Pointer Reference
Must it be initialized when declared? No, but start it at nullptr Yes
Can it be null? Yes: it can hold nullptr No
Can it be made to refer to something else? Yes: p = &best; No: it stays with its first object
How do you reach the value? *p, or p->score for a member By its name, like the variable itself

The same function, written both ways:

[Example 5:
void add_bonus(int* score) {
    if (score) {     // a pointer may be null: check first
        *score += 5;
    }
}

void add_bonus(int& score) {
    score += 5;      // a reference always refers to an int
}

// ...

int main() {
    int score = 90;

    add_bonus(&score);           // pass the address
    std::cout << score << '\n';  // 95

    add_bonus(score);            // pass the variable itself
    std::cout << score << '\n';  // 100

    add_bonus(nullptr);          // allowed, and does nothing
    std::cout << score << '\n';  // 100
    // ...
}

— end example]

It printed 95, 100 and 100. add_bonus(nullptr) calls the pointer version, since a reference cannot be null, and the check turns the call into nothing.

Through a pointer to a struct, the arrow reaches a member: p->score is (*p).score, as struct in C++ shows.

For a parameter that only reads its argument, choosing between pass by value and const reference is the next question. Pointers are also the links of structures such as a linked list, where each node points at the next.

Do you need new and delete in C++?

Usually not. new creates an object that lives until delete is called on its pointer. Modern C++ lets a std::unique_ptr own the object instead: std::make_unique, from <memory>, creates it, and the unique_ptr deletes it when it goes out of scope:

[Example 6:
int* raw = new int{42};     // lives until delete
std::cout << *raw << '\n';  // 42
delete raw;                 // give the memory back

auto owned = std::make_unique<int>(42);
std::cout << *owned << '\n';  // 42
// no delete: owned deletes the int when it goes away

— end example]

Both printed 42. Forget the delete and the program never gives the memory back, which is called a leak; call delete twice on the same pointer and the behavior is undefined. With std::unique_ptr there is no delete to forget or to repeat, and *owned reads the value as *raw does.

Common pointer mistakes in C++

Most pointer mistakes follow a pointer to an object that is not there. The plainest case, returning the address of a local variable from its function, is one the compilers warn about:

[Example 7:
int* make_score() {
    int score = 90;
    return &score;  // score is destroyed when the function returns
}

— end example]

GCC 16.2 said:

[Example 8:
warning: address of local variable 'score' returned [-Wreturn-local-addr]

— end example]

MSVC 19.44 and both Clang builds warned too, at the warning flags the samples use. The checklist, with the fix for each:

  • A pointer never given a value. int* p; inside a function starts with an indeterminate value: reading it, even to test it with if (p), is undefined behavior, and *p could reach any place. Start every pointer at an address or at nullptr.
  • A null pointer followed with *: undefined behavior, which may crash the program or may not. When a pointer may be null, test it with if (p) first.
  • A dangling pointer, one that still holds the address of something that no longer exists: a local variable after its function has returned, as above, or an object after delete. Dereferencing it, or calling delete on it again, is undefined behavior; let a std::unique_ptr own the object and make the one delete for you.

The Language section has the other answers on the core language.

Questions

What is the size of a pointer in C++?

The standard does not fix it. On the four 64-bit builds used here (MSVC and Clang on Windows x64, GCC and Clang on Linux x86-64), sizeof(int*), sizeof(char*) and sizeof(double*) were all 8 bytes: the pointer had the same size whether it pointed at a char, an int or a double.

What does -> mean in C++?

The arrow reaches a member of a struct or class through a pointer to it: p->score is short for (*p).score. With Player* p = &ada;, the line p->score = 97; changes ada.score to 97.

What is the difference between int* p and int *p?

None: the space is not significant, and both declare p as a pointer to int. The star belongs to the name after it, not to int, so in int* a, b; the variable a is a pointer and b is a plain int, as all four compilers used here confirmed. Declare one pointer per line and the question never comes up.

Why do you need pointers in C++?

To refer to an object without copying it when there may be no object at all, as a search that returns nullptr for not found does; to switch to a different object later; to step through an array; and to link objects together, as the nodes of a linked list do. When a function only needs the caller’s object, a reference does the first job more simply.

What is a void pointer in C++?

A void* is a pointer that does not say what type of object it points at, so it must be converted to a pointer of the right type, such as int*, before the value can be reached. Any object pointer converts to a void* without a cast; getting the int* back takes a static_cast.

What is a pointer to a pointer?

A variable that holds the address of a pointer variable: if p is an int*, then int** pp = &p; makes pp point at p. Dereferencing pp once gives p; dereferencing it twice, **pp, gives the int that p points at.

Sources

  1. Pointer declarationcppreference.com
  2. nullptr, the pointer literalcppreference.com
  3. [expr.unary.op] Unary operatorsC++ standard working draft
  4. [expr.add] Additive operatorsC++ standard working draft
  5. new expressioncppreference.com
  6. std::unique_ptrcppreference.com
  7. std::make_uniquecppreference.com
  8. Default-initializationcppreference.com

Read next

  1. const in C++: variables, pointers, references, methodsLanguage · Miriam Castellanos · written for C++17
  2. assert in C++: how assert and static_assert workLanguage · Miriam Castellanos · written for C++17
  3. Destructor in C++: what it is and when it runsLanguage · Miriam Castellanos · written for C++17