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
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.

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:
#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.

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:
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:
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):
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:
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:
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:
int* make_score() {
int score = 90;
return &score; // score is destroyed when the function returns
}— end example]
GCC 16.2 said:
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 withif (p), is undefined behavior, and*pcould reach any place. Start every pointer at an address or atnullptr. - A null pointer followed with
*: undefined behavior, which may crash the program or may not. When a pointer may be null, test it withif (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 callingdeleteon it again, is undefined behavior; let astd::unique_ptrown the object and make the onedeletefor 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
- Pointer declarationcppreference.com
- nullptr, the pointer literalcppreference.com
- [expr.unary.op] Unary operatorsC++ standard working draft
- [expr.add] Additive operatorsC++ standard working draft
- new expressioncppreference.com
- std::unique_ptrcppreference.com
- std::make_uniquecppreference.com
- Default-initializationcppreference.com