Pointers
A pointer is a variable that holds an address. The address-of operator gives one, the unary * reads the object at it, and nullptr is the value that points at nothing. References of lesson 011 are built on the same idea with a friendlier syntax.
The course put pointers after fifteen lessons on purpose: a reference does most of the work, and a pointer is needed when the target may be absent, may change, or is an array. This part of the course uses them where they are the right tool, and then replaces most of them with the smart pointers of lesson 091.
Address and dereference
&value gives the address of value and *pointer reads the object at that address. The two operators are inverses, and a write through the pointer is a write to the original variable.
The program prints the value, changes it through the pointer and prints it again. The address itself is printed as a comparison with the original address, not as a number, because the number changes with every run.
A pointer has a type, and the type decides how the bytes at the address are read. An int* reads four bytes as an int; a char* reads one byte. That is why a cast between pointer types is a promise about the memory.
#include <iostream>
int main()
{
int value = 41;
int* pointer = &value;
std::cout << *pointer << ' ' << (pointer == &value) << '\n';
*pointer = 42;
std::cout << value << '\n';
}
g++ -std=c++23 -Wall -Wextra address.cpp -o address
./address
41 1
42
nullptr
A pointer that points at nothing holds nullptr. It is a value of its own type, not the integer 0, and a test if (pointer) asks whether it points at something.
The program has a function that returns nullptr when it finds nothing, and main checks the result before dereferencing it. The output is the number of found and missing lookups.
Dereferencing nullptr is undefined behaviour and usually a segmentation fault, which lesson 083 showed from the debugger. The check is the caller's job because the type carries no guarantee that the pointer is valid.
#include <iostream>
#include <vector>
int* find_positive(std::vector<int>& values)
{
for (int& value : values) {
if (value > 0) {
return &value;
}
}
return nullptr;
}
int main()
{
std::vector<int> values{-3, 0, 7};
std::vector<int> empty{};
int* found = find_positive(values);
int* missing = find_positive(empty);
std::cout << (found != nullptr) << ' ' << *found << ' ';
std::cout << (missing == nullptr) << '\n';
}
g++ -std=c++23 -Wall -Wextra nullptr.cpp -o nullptr_demo
./nullptr_demo
1 7 1
A pointer parameter
A function that takes a pointer can change the caller's object, exactly as a reference parameter does. The difference is visible at the call: bump(&value) shows that an address is passed.
The program passes the address of a counter and the function adds to it. The output is the changed value, and the same function could be called with nullptr if the caller had nothing to pass.
That last possibility is the reason to prefer a reference when the target must exist. A pointer parameter documents that the argument may be absent, and then the function has to check it.
#include <iostream>
void add_one(int* value)
{
if (value != nullptr) {
*value += 1;
}
}
int main()
{
int count = 41;
add_one(&count);
add_one(nullptr);
std::cout << count << '\n';
}
g++ -std=c++23 -Wall -Wextra parameter.cpp -o parameter
./parameter
42
A pointer to a struct
A pointer to a struct reads its members with ->, which is (*pointer).member in one operator. The arrow is the reason a pointer to a struct looks different from a struct value.
The program's point is created on the stack and its address passed to a function that moves it. The function changes the original, and the printed coordinates show the move.
The same expression works for a pointer to a class with member functions. A pointer to a const object uses a pointer to const, and then only the const member functions can be called through it.
#include <iostream>
struct point
{
int x;
int y;
};
void move(point* p, int dx, int dy)
{
p->x += dx;
p->y += dy;
}
int main()
{
point p{3, 4};
move(&p, 10, 20);
std::cout << p.x << ' ' << p.y << '\n';
}
g++ -std=c++23 -Wall -Wextra struct.cpp -o struct_ptr
./struct_ptr
13 24
An integer is not an address
A pointer is not an integer, and the compiler does not convert one into the other. The program assigns 5 to an int* and the compiler reports that an int cannot be converted to a pointer.
The message names the target type and the value, and the fix is &value or a new int, depending on what the pointer is supposed to point at. A cast from an integer is possible and almost always a bug.
The same rule protects the program from a whole class of mistakes that C allows: an address and a number are different things, and a modern compiler keeps them apart.
int main()
{
int* pointer = 5;
return *pointer;
}
g++ -std=c++23 -Wall -Wextra integer.cpp -o integer
integer.cpp: In function 'int main()':
integer.cpp:3:20: error: invalid conversion from 'int' to 'int*' [-fpermissive]
3 | int* pointer = 5;
| ^
| |
| int
Exercises
- Write a function that takes two pointers and swaps the values they point at.
- Change find_positive to return a pointer to const and check that main still compiles.
- Print the size of a pointer and of the int it points at, and explain the difference.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
&valuedo?Answer
It produces the address of the variable
The address-of operator gives the address, which is the value a pointer stores.
-
What does this program print?
int value = 41; int* p = &value; *p = 42; std::cout << value;Answer
42
A write through the pointer writes the variable, so value is 42.
-
What is
nullptr?Answer
A pointer value that points at no object
nullptr is a value of its own type that no object has as its address; it converts to any pointer type.
-
What does
p->xmean for a pointer p to a struct with a member x?Answer
(*p).xThe arrow dereferences the pointer and reads the member in one operator;
p.xwould need p to be a struct, not a pointer. -
What does this function print when called with
&countwhere count is 41, and then withnullptr?void add_one(int* v) { if (v) *v += 1; }Answer
42
The first call increments count to 42; the second call finds nullptr and does nothing.
-
What does the compiler do with
int* pointer = 5;?Answer
It refuses: an int cannot be converted to a pointer
There is no implicit conversion from an integer to a pointer, so the declaration is an error.
-
When is a pointer parameter a better choice than a reference parameter?
Answer
When the argument may be absent and nullptr is meaningful
A reference always names an object; a pointer can be nullptr, which is exactly the case where the function has to accept nothing.
-
What does this program print?
int* p = nullptr; std::cout << (p != nullptr) << ' ' << (p == nullptr);Answer
0 1
The pointer is null, so the first comparison is false (0) and the second is true (1).
-
What is the type of
&valuewhen value is aconst int?Answer
const int*The address of a const object is a pointer to const: the object cannot be written through it.
-
A function dereferences its pointer parameter without checking it, and a caller passes nullptr. What happens?
Answer
The program has undefined behaviour, usually a crash
Dereferencing a null pointer is undefined behaviour; nothing checks it for you, which is why the function itself must.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev