C++: Pointers
Understand memory addresses, pointer arithmetic, dynamic allocation, and smart pointers, the foundation of low-level C++ programming.
What Are Pointers?
A pointer is a variable that stores the memory address of another variable. Instead of holding a value like 42, it holds an address like 0x7fff5abc, the location in RAM where a value lives.
Pointers
Every variable in a running program occupies a specific address in memory, a pointer simply stores that address so you can reach the variable indirectly.
- Pointers enable dynamic memory allocation, efficient array handling, and data structures like linked lists
- A pointer's type (int*, double*) determines how many bytes are read when it is dereferenced
- All pointer variables themselves are the same size (8 bytes on 64-bit systems)
- Pointers are powerful but dangerous, always initialize them before use
Declaring Pointers, & and * Operators
Declare a pointer by adding * after the type. Use & (address-of) to get a variable's address and store it in the pointer. Use * (dereference) to read or write the value at that address.
& and * Operators
& and * are inverses: & goes from variable to address; * goes from address back to value.
- int* p; declares a pointer to int
- &x: address-of operator, produces the address of variable x
- *p: dereference operator, reads or writes the value at the address p holds
- Declare and assign in one line: int* p = &x;
Pointer Declaration, & and *
C++Get an address, dereference it to read, and dereference it to write.
Null Pointers
A null pointer holds the value nullptr, it does not point to any valid memory. Always initialize pointers to nullptr when you have no valid address yet, and check for null before dereferencing.
nullptr
Dereferencing a null pointer is undefined behavior and typically causes a segmentation fault, always guard with a null check.
- Use nullptr (C++11) instead of NULL or 0 for clarity and type safety
- int* p = nullptr; is safe to declare, just never dereference it without checking
- Check before use: if (p != nullptr) { *p; }
- Functions that may fail to return a valid address often return nullptr
Null Pointer Guard
C++Always check for nullptr before dereferencing.
void Pointers
A void* is a generic pointer that can hold the address of any type. It cannot be dereferenced directly, you must cast it to the correct type first. Common in C-style APIs like memcpy.
void*
void* gives up type information, you are responsible for casting it to the correct type before use, otherwise the result is undefined.
- Can store the address of any type without implicit conversion
- Must be cast before dereferencing: int* ip = static_cast<int*>(vp);
- Used in low-level memory operations (malloc, memcpy) and callback APIs
- Prefer templates over void* in modern C++ for type-safe generics
void Pointer
C++Store an int address in a void*, then cast back to int* to dereference.
Pointer Arithmetic
Adding an integer to a pointer advances it by that many elements (not bytes). Since array elements are contiguous in memory, a pointer to the first element can be incremented to walk through the entire array.
Pointer Arithmetic
ptr + n moves forward by n * sizeof(*ptr) bytes, the compiler scales the arithmetic by the pointed-to type's size automatically.
- ptr + 1 points to the next element of the same type
- ptr - 1 points to the previous element
- ptr2 - ptr1 gives the number of elements between two pointers (same array only)
- An array name is a constant pointer to its first element: arr == &arr[0]
Pointer Arithmetic
C++Walk an array using pointer offsets instead of index notation.
Array of Pointers and Pointer to Array
An array of pointers stores multiple addresses in an array. A pointer to an array holds the address of an entire array and is used when passing 2D arrays to functions.
Array of Pointers vs Pointer to Array
An array of pointers (int* arr[]) is most useful for arrays of strings; a pointer to array (int (*p)[]) is used with 2D array parameters.
- int* arr[3]: array of 3 int pointers
- int (*p)[3]: pointer to an array of 3 ints
- Parentheses matter: int* p[3] vs int (*p)[3] are very different
- Array of pointers is common for managing a list of strings (char*[])
Array of Pointers
C++Three pointers stored in an array, each pointing to a separate int.
Pointers as Function Arguments
Passing a pointer to a function gives it the address of the caller's variable. The function can then modify the original value through dereferencing, equivalent in effect to pass-by-reference, but explicit about the address.
Pointer Parameters
Passing a pointer is the C-style way to allow a function to modify the caller's data, in modern C++, prefer references unless nullptr is a valid state.
- The caller passes &variable; the function receives type* param
- Dereference with *param inside the function to read or write
- Always null-check pointer parameters before dereferencing
- Pointer parameters are common in C APIs and legacy code
Pointer as Function Argument
C++square() modifies x in place through its pointer parameter.
Returning Pointers from Functions
A function can return a pointer, but it must point to memory that outlives the function call, either a global, a static variable, or heap-allocated memory. Never return a pointer to a local variable.
Returning Pointers
Returning a pointer to a local variable is a dangling pointer, the local is destroyed when the function returns, leaving the pointer pointing to invalid memory.
- Safe to return: pointer to global, static, or heap (new) memory
- Unsafe: int x = 5; return &x;, x is destroyed when function exits
- Caller is responsible for delete-ing heap memory returned from a function
- Consider returning std::unique_ptr instead for automatic cleanup
Returning a Heap Pointer
C++Allocate on the heap so the memory is valid after the function returns.
Function Pointers
A function pointer stores the address of a function. It allows you to pass functions as arguments, store them in data structures, and call them indirectly, the foundation of callbacks and strategy patterns in C.
Function Pointers
The syntax for function pointers is dense, use a typedef or auto to make them readable in modern C++.
- Syntax: returnType (*ptrName)(paramTypes);
- Assign: ptrName = &functionName; (& is optional for function names)
- Call: ptrName(args); or (*ptrName)(args);
- In modern C++, std::function and lambdas are cleaner alternatives
Function Pointer
C++Point to add then mul, same pointer, different functions.
new and delete
new allocates memory on the heap and returns a pointer to it. delete releases that memory. Heap memory persists until explicitly freed, unlike stack variables that are destroyed when they go out of scope.
new and delete
Every new must have exactly one matching delete, too few causes a memory leak; too many causes undefined behavior (double-free).
- int* p = new int(10); allocates one int on the heap, initialized to 10
- delete p; frees the memory and invalidates p
- Set p = nullptr after delete to prevent accidental reuse (dangling pointer)
- Use new only when the lifetime must extend beyond the current scope
new and delete
C++Allocate one int on the heap, use it, then free it.
new[] and delete[] for Arrays
To allocate an array on the heap, use new[] with a size. Always free it with delete[], using plain delete on an array is undefined behavior.
new[] and delete[]
Mixing new/delete with new[]/delete[] is undefined behavior, always match them exactly.
- int* arr = new int[n]; allocates n ints on the heap
- Access with arr[i] just like a normal array
- delete[] arr; frees the entire array
- The size n can be a runtime variable, unlike stack arrays which need a compile-time size
Dynamic Array with new[] and delete[]
C++Allocate a runtime-sized array on the heap and free it with delete[].
Memory Leaks
A memory leak occurs when heap memory is allocated with new but never released with delete. The memory remains occupied until the program ends, and in long-running programs this can exhaust available memory.
Memory Leaks
The best way to prevent leaks is to avoid raw new/delete entirely, use RAII types like std::vector, std::string, and smart pointers instead.
- A leak occurs when a pointer is reassigned or goes out of scope before delete is called
- Leaks do not crash immediately, they silently consume more and more memory over time
- Tools like Valgrind and AddressSanitizer detect leaks at runtime
- Smart pointers (unique_ptr, shared_ptr) automatically delete when they go out of scope
Avoiding Memory Leaks
C++Delete before reassigning a pointer, otherwise the original allocation is lost.
Double Pointers (Pointer to Pointer)
A double pointer (int**) stores the address of another pointer. It is used when a function needs to modify a pointer itself (not just the value it points to), and for dynamically allocated 2D arrays.
int** (Double Pointer)
Think of it as a chain: ** leads from the double pointer to a pointer, and then to the actual value.
- int** pp = &p;, pp holds the address of pointer p
- *pp dereferences once to get the int* pointer
- **pp dereferences twice to get the int value
- Used to allocate 2D arrays dynamically and to modify pointers inside functions
Double Pointer
C++Three levels: variable, pointer, double pointer, all referring to the same int.
this Pointer
Inside a class member function, this is an implicit pointer to the object the method was called on. It is used to disambiguate between a member variable and a parameter with the same name, and to return the current object from a method.
this Pointer
this is automatically available in every non-static member function, you rarely need to write it explicitly unless resolving a name conflict or returning *this for method chaining.
- Type: ClassName* const this
- Disambiguate: this->name = name; when param and member share a name
- Return the current object: return *this; enables method chaining
- Not available in static member functions (they have no associated object)
this Pointer and Method Chaining
C++Return *this from each method to allow chained calls.
Smart Pointers (C++11): Overview
Smart pointers are class wrappers around raw pointers that automatically call delete when they go out of scope. They live in the <memory> header and eliminate the need to manually manage heap memory.
Smart Pointers
Prefer smart pointers over raw new/delete in all modern C++ code, they make memory management automatic and exception-safe.
- unique_ptr: sole ownership, only one pointer can own the resource; deleted when it goes out of scope
- shared_ptr: shared ownership, reference-counted; deleted when the last owner goes out of scope
- weak_ptr: non-owning observer of a shared_ptr, breaks circular references
- Create with make_unique<T>() and make_shared<T>(), never use new directly with smart pointers
unique_ptr and shared_ptr
C++Smart pointers free their memory automatically when they go out of scope.
Smart Pointer Comparison
| Type | Ownership | Copyable | Use Case |
|---|---|---|---|
| unique_ptr | Sole owner | No (moveable only) | Exclusive ownership of a resource |
| shared_ptr | Shared owners | Yes (ref-counted) | Shared ownership across multiple owners |
| weak_ptr | No ownership | Yes | Breaking shared_ptr circular references |
Knowledge Check
1. What does the address-of operator & return?
2. What does dereferencing a pointer with * do?
3. What does ptr + 1 do when ptr is an int*?
4. What must you call to free memory allocated with new[]?
5. What is a memory leak?
6. What does a double pointer (int**) store?
7. Which smart pointer gives shared ownership of a resource?