Ch5.15: Common Pointer Pitfalls
Overview
Pointers are powerful but extremely easy to misuse. Many bugs in C++ programs come from incorrect assumptions about pointer validity, lifetime, aliasing, alignment, and arithmetic. This chapter collects the most common mistakes so you can avoid them.
You will learn:
- dangling pointers
- null pointer misuse
- off-by-one errors
- misaligned pointers
- strict aliasing violations
- overlapping memory hazards
- pointer arithmetic mistakes
- lifetime issues
- type punning mistakes
- incorrect use of memfunctions
- pointer comparison pitfalls
1. Dangling pointers
A dangling pointer points to an object that no longer exists.
int const *p;
{
int x{42};
p = ::std::addressof(x);
} // x is destroyed here
// *p; // ❌ dangling pointer
A pointer is only valid as long as the object it points to is alive.
2. Null pointer misuse
Dereferencing nullptr is always undefined behavior.
int *p{nullptr};
// *p = 10; // ❌ undefined behavior
Also, passing nullptr to memcpy, memmove,
memcmp, or memchr with a non-zero size is undefined
behavior (see Ch5.14).
3. Off-by-one errors
Pointer arithmetic moves in units of the pointed-to type.
int a[3]{1,2,3};
int *p = a;
int *end = a + 3; // one past the end (valid but not dereferenceable)
// *end; // ❌ undefined behavior
Only the range [a, a + 3) is valid for dereferencing.
4. Misaligned pointers
Some types require stricter alignment than others. Casting a pointer to a type with stricter alignment requirements can cause undefined behavior.
unsigned char buffer[8]{};
// int *p = reinterpret_cast(buffer + 1); // ❌ misaligned
Misaligned access is not allowed for most types.
5. Strict aliasing violations
Accessing an object through a pointer of an unrelated type is undefined behavior (see Ch5.13).
float f{1.0f};
int *p = reinterpret_cast(::std::addressof(f));
// *p; // ❌ strict aliasing violation
6. Overlapping memory hazards
Using memcpy on overlapping memory is undefined behavior.
// memcpy(dst, src, n); // ❌ if ranges overlap
Use memmove when unsure.
7. Pointer arithmetic mistakes
Pointer arithmetic is in units of the pointed-to type, not bytes.
int a[3]{1,2,3};
int *p = a;
int *q = p + 1; // moves by sizeof(int) bytes
Also, sizeof(pointer) is not the size of the pointed-to object.
8. Lifetime issues
A pointer does not extend the lifetime of the object it points to.
int const *p;
{
int x{10};
p = ::std::addressof(x);
}
// *p; // ❌ x is gone
9. Type punning mistakes
Using reinterpret_cast for type punning is almost always undefined
behavior. Use memcpy or ::std::bit_cast instead.
10. Incorrect use of memfunctions
Common mistakes include:
- forgetting to check
size != 0 - passing
nullptrwith non-zero size - using
memcpyon overlapping memory - using
memsetto clear secrets (may be optimized away)
11. Pointer comparison pitfalls
Ordering comparisons (<, >) are only valid for
pointers into the same array.
// p < q; // ❌ undefined behavior if p and q are unrelated
Key takeaways
- A pointer is only valid while the object it points to is alive.
- Never dereference
nullptr. - Pointer arithmetic is easy to misuse.
- Misalignment and strict aliasing violations cause undefined behavior.
- Use
memmovefor overlapping memory. - Use
memcpyor::std::bit_castfor type punning. - Use
::fast_io::secure_clearto erase secrets. - Pointer comparisons are only valid within the same array.