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
constpointer forms - Recognise dangling pointers, leaks and null dereferences, and know that smart pointers help
On this page
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.
#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).
| Feature | Pointer T* | Reference T& |
|---|---|---|
| Can be null | Yes (nullptr) | No |
| Can be reassigned to another object | Yes | No, it is bound once |
| Must be initialised at declaration | No, but you should | Yes |
| Access the target | *ptr or ptr->member | Use it like the original variable |
| Typical use | Optional values, dynamic memory, data structures | Function parameters, aliases |
#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.
| Declaration | Read as | Can change the pointed-to value? | Can repoint the pointer? |
|---|---|---|---|
const int* p | pointer to a const int | No | Yes |
int* const p | const pointer to an int | Yes | No |
const int* const p | const pointer to a const int | No | No |
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.
#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. Checkptr != nullptrfirst. - Dangling pointers. After
delete p;the pointer still holds the old address. Using it is undefined behavior. Set it tonullptrafter deleting, or better, use a smart pointer. - Memory leaks.
newwithout a matchingdeleteleaks memory for the life of the program. Tools like Valgrind or the-fsanitize=addresscompiler flag can detect this. - Mismatched delete. Memory from
new[]must be freed withdelete[], notdelete. 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 warnsaddress of local variable 'x' returned. - Copying a unique_ptr.
auto b = a;fails witherror: use of deleted functionmentioningunique_ptr. Move it withstd::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:
&xgets it and*pfollows it. Initialise every pointer, usingnullptrfor none. - A reference is a non-null alias bound once at declaration; prefer it for function parameters.
- Read
constwith pointers right to left:const int*protects the value,int* constprotects the pointer. - Prefer
std::unique_ptrand containers over rawnewanddeleteto 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.
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.