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:

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:

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