C++ tutorials  /  Pointers in C++
Chapter 8 · C++

Pointers in C++

A pointer in C++ is a variable that stores the memory address of another variable, rather than storing a data value directly. Pointers are declared using the '*' symbol and allow programs to indirectly access and manipulate the value at a specific memory location, enabling dynamic memory management, efficient array/function handling, and the building of complex data structures like linked lists and trees.

Imagine your house has a street address, and a pointer is like a sticky note with that address written on it. The sticky note itself isn't your house — it just tells you where to find it. Similarly, a variable stores an actual value (like 25), but a pointer stores the memory address of where that value lives, not the value itself. You can follow that address (called 'dereferencing') to go look at, or even change, what's stored there. This indirection is powerful because it lets multiple parts of a program refer to and modify the exact same piece of data without copying it around.

Operating systems use pointers extensively to manage memory efficiently — when you open a large video file, the video player uses pointers to reference chunks of the file in memory rather than copying gigabytes of data around every time a function needs to process it. Linked lists, which power features like a music app's 'up next' queue, are built entirely using pointers, where each song node points to the memory address of the next song node. Game engines use pointers to let multiple game objects reference the same shared resource (like a 3D character model or texture) without duplicating that heavy data in memory for every single instance of the character on screen. Dynamic memory allocation, like loading an unknown number of user records from a database at runtime, relies on pointers to manage memory that's allocated and sized only when the program actually runs.

Without pointers, C++ programs couldn't efficiently share or modify data across different functions and parts of a program without expensive copying, couldn't allocate memory dynamically at runtime based on data that's only known while the program is running, and couldn't build fundamental data structures like linked lists, trees, and graphs that rely on nodes referencing other nodes via memory addresses. Pointers give C++ developers direct, fine-grained control over memory — a defining feature that enables the language's high performance in systems programming, but which also demands careful, disciplined handling to avoid serious bugs like memory leaks and crashes.

  • Null Pointers: A pointer explicitly initialized to point to nothing (using 'nullptr' in modern C++), used to indicate that a pointer currently doesn't reference any valid memory location, and commonly checked before dereferencing to avoid undefined behavior.
  • Void Pointers: A generic pointer type ('void*') that can point to an object of any data type, but cannot be dereferenced directly without first being cast to a specific type, often used in low-level or generic memory-handling functions.
  • Wild/Dangling Pointers: A pointer that either was never initialized (wild pointer, pointing to a random/garbage address) or points to memory that has already been freed or gone out of scope (dangling pointer), both of which cause undefined behavior if dereferenced and are a common source of hard-to-debug crashes.
  • Pointers to Pointers (Double Pointers): A pointer that stores the address of another pointer, denoted with '**' (e.g., 'int **ptr'), used in scenarios like dynamically allocating multi-dimensional arrays or when a function needs to modify a pointer's own value (not just what it points to).
  • Smart Pointers (Modern C++): Classes introduced in C++11 (like 'std::unique_ptr' and 'std::shared_ptr') that wrap raw pointers and automatically manage their memory lifetime, deallocating memory automatically when it's no longer needed, greatly reducing the risk of memory leaks compared to manually managing raw pointers with 'new' and 'delete'.
// Pointer declaration dataType *pointerName; // Storing an address in a pointer int value = 10; int *ptr = &value; // & gets the address of 'value' // Dereferencing (accessing the value at the pointed address) cout << *ptr; // prints 10 *ptr = 20; // modifies 'value' through the pointer // Null pointer int *nullPtr = nullptr; // Dynamic memory allocation int *dynamicPtr = new int(5); delete dynamicPtr; // free the memory when done
A developer wants to write a function that swaps two integer values using pointers instead of references to understand the lower-level mechanics of indirection, and also needs to dynamically allocate memory for an array whose size is only known at runtime (based on user input), which cannot be done with a fixed-size built-in array.
Basic Pointer Declaration, Address-of, and Dereferencing
Demonstrates declaring a pointer, storing a variable's address in it, and using dereferencing to both read and modify the original variable indirectly.
cpp
#include <iostream> using namespace std; int main() { int value = 25; int *ptr = &value; cout << "Value: " << value << endl; cout << "Address of value: " << &value << endl; cout << "Pointer stores: " << ptr << endl; cout << "Dereferenced pointer: " << *ptr << endl; *ptr = 50; cout << "Value after modifying via pointer: " << value << endl; return 0; }
Value: 25 Address of value: 0x7ffe5a3c9a8c Pointer stores: 0x7ffe5a3c9a8c Dereferenced pointer: 25 Value after modifying via pointer: 50
'&value' retrieves the memory address of 'value', which is stored in the pointer 'ptr'. Printing 'ptr' directly shows the address (which will vary each run), while '*ptr' dereferences the pointer to access the actual value stored there. Assigning '*ptr = 50;' modifies the original 'value' variable indirectly through the pointer, proving that the pointer and 'value' refer to the exact same memory location. Note: the actual hexadecimal address shown will differ every time the program runs.
Dynamic Memory Allocation Using new and delete
Shows how to allocate memory at runtime for a value whose size isn't known until the program executes, and properly freeing it afterward.
cpp
#include <iostream> using namespace std; int main() { int size; cout << "Enter array size: "; cin >> size; int *arr = new int[size]; for (int i = 0; i < size; i++) { arr[i] = i * 10; } for (int i = 0; i < size; i++) { cout << arr[i] << " "; } cout << endl; delete[] arr; return 0; }
Enter array size: 4 0 10 20 30
'new int[size]' dynamically allocates an array on the heap with a size determined at runtime by user input — something a fixed built-in array declared like 'int arr[size]' cannot safely do with a non-constant size in standard C++. After using the array, 'delete[] arr;' frees the allocated memory back to the system, which is essential to prevent memory leaks since the compiler does not automatically reclaim heap memory.
  • Dereferencing an Uninitialized or Null Pointer: Declaring a pointer without initializing it (e.g., 'int *ptr;') and then dereferencing it with '*ptr' accesses a random, unpredictable memory address, causing undefined behavior or a crash. Similarly, dereferencing a pointer explicitly set to 'nullptr' also crashes the program. Always initialize pointers and check for null before dereferencing when the pointer might not point to valid memory.
  • Memory Leaks from Forgetting to delete Dynamically Allocated Memory: Every 'new' must be paired with a corresponding 'delete' (and 'new[]' with 'delete[]'). Forgetting to free dynamically allocated memory means that memory remains reserved and unusable for the rest of the program's execution, which in long-running applications (like servers) gradually exhausts available memory, a bug known as a memory leak.
  • Using a Dangling Pointer After Its Memory Has Been Freed: After calling 'delete ptr;', the memory 'ptr' pointed to is released, but 'ptr' itself still holds that (now invalid) address unless manually reset. Continuing to use 'ptr' afterward — a 'dangling pointer' — leads to undefined behavior, since that memory may have already been reallocated for something else entirely. Always set pointers to 'nullptr' immediately after deleting them.
  • Double-Deleting the Same Pointer: Calling 'delete' twice on the same pointer (without reallocating it in between) causes undefined behavior and often crashes the program, since the memory manager's internal bookkeeping for that memory block has already been invalidated after the first deletion.
  • Confusing Pointer Arithmetic with Regular Arithmetic: When you add 1 to a pointer (e.g., 'ptr + 1'), it doesn't simply add 1 byte to the address — it advances the pointer by the SIZE of the data type it points to (e.g., adding 4 bytes for an 'int*' on most systems). Beginners often forget this and miscalculate offsets when manually navigating arrays via pointer arithmetic.
  • Prefer Smart Pointers Over Raw Pointers for Dynamic Memory Management: In modern C++ (C++11 and later), use 'std::unique_ptr' or 'std::shared_ptr' from '<memory>' instead of manually managing raw pointers with 'new'/'delete', since smart pointers automatically deallocate memory when it's no longer needed, virtually eliminating memory leaks and dangling pointer bugs in typical use cases.
  • Always Initialize Pointers, Even If Just to nullptr: Never leave a pointer declared without an initial value — set it to 'nullptr' if it doesn't yet point to valid memory, so that any accidental dereference before proper assignment is more likely to cause an immediately obvious crash (null pointer dereference) rather than silent memory corruption from a wild pointer.
  • Set Pointers to nullptr Immediately After delete: After freeing memory with 'delete ptr;', immediately follow up with 'ptr = nullptr;' to prevent the pointer from becoming a dangling reference to freed memory, making any accidental subsequent use immediately detectable rather than silently corrupting data.
  • Match Every new with Exactly One delete (and new[] with delete[]): Track every dynamic allocation carefully and ensure a corresponding deallocation exists on every code path (including error/exception paths), and never mix 'new'/'delete' with 'new[]'/'delete[]' — using the wrong pairing (e.g., 'delete' on an array allocated with 'new[]') causes undefined behavior.
What is the difference between a pointer and a reference in C++?
A pointer is a variable that stores a memory address and can be reassigned to point to different variables during its lifetime, can be null, and requires explicit dereferencing ('*') to access the value it points to. A reference is an alias for an existing variable, must be initialized when declared, cannot be reassigned to refer to a different variable afterward, cannot be null (in well-formed code), and is used directly without explicit dereferencing syntax, making references generally simpler and safer for cases where reseating isn't needed.
What is a dangling pointer, and how can it be avoided?
A dangling pointer is a pointer that still holds the address of memory that has already been freed (via 'delete') or has gone out of scope (like a local variable's address after the function returns), meaning the memory it points to is no longer valid or guaranteed to contain the expected data. It can be avoided by setting pointers to 'nullptr' immediately after deleting the memory they point to, avoiding returning addresses of local stack variables from functions, and preferring smart pointers, which automatically manage the underlying memory's lifetime.
Explain the difference between 'new'/'delete' and 'malloc'/'free' in C++.
'new' and 'delete' are C++ operators that allocate/deallocate memory AND automatically call the constructor/destructor of the object being created or destroyed, making them type-safe (they return a properly typed pointer without requiring a cast) and object-lifecycle-aware. 'malloc' and 'free', inherited from C, only allocate/deallocate raw memory blocks without calling any constructors or destructors, return a generic 'void*' that must be manually cast to the correct type, and are generally discouraged in idiomatic modern C++ in favor of 'new'/'delete' or, better yet, smart pointers and STL containers.
What is pointer arithmetic, and how does it relate to array traversal in C++?
Pointer arithmetic refers to performing addition or subtraction on a pointer, where the pointer advances by a number of bytes equal to (offset * sizeof(pointed-to type)) rather than a raw byte count — for example, 'ptr + 1' on an 'int*' moves the address forward by 4 bytes (on most systems), landing exactly on the next integer element. Since array names decay into pointers to their first element, array indexing like 'arr[i]' is actually syntactic sugar for the pointer arithmetic expression '*(arr + i)', which is why arrays and pointers are so closely intertwined in C++.
What is a void pointer, and what are its limitations?
A void pointer ('void*') is a generic pointer type that can hold the address of any data type without an implicit conversion, commonly used in generic or low-level memory-handling code (like custom memory allocators) where the specific type isn't known or relevant at that point. Its main limitation is that it cannot be dereferenced directly, since the compiler has no way of knowing how many bytes to read or how to interpret the data at that address — it must first be explicitly cast to a specific pointer type (e.g., 'static_cast<int*>(voidPtr)') before the underlying value can be accessed.
Write a C++ function that swaps two integers using pointers (not references), and call it from main() to demonstrate the swap.
#include <iostream> using namespace std; void swapValues(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 10, y = 20; cout << "Before: x=" << x << " y=" << y << endl; swapValues(&x, &y); cout << "After: x=" << x << " y=" << y << endl; return 0; }
Write a program that dynamically allocates an integer, assigns it a value through the pointer, prints it, and then correctly frees the memory.
#include <iostream> using namespace std; int main() { int *ptr = new int; *ptr = 42; cout << "Dynamically allocated value: " << *ptr << endl; delete ptr; ptr = nullptr; return 0; }
Explain what is wrong with the following code and how to fix it: int* createArray() { int localArr[5] = {1, 2, 3, 4, 5}; return localArr; }
This code returns a pointer to 'localArr', which is a local array allocated on the function's stack frame. Once 'createArray()' returns, its stack frame is destroyed, meaning the returned pointer becomes a dangling pointer pointing to invalid, deallocated memory — using it afterward causes undefined behavior. The fix is to either dynamically allocate the array on the heap using 'new int[5]{1,2,3,4,5};' and return that pointer (remembering to 'delete[]' it later), or better yet, return a 'std::vector<int>' by value, which safely manages its own memory and avoids manual pointer handling entirely.

Pointers are variables that store memory addresses, giving C++ developers direct, low-level control over memory — enabling dynamic memory allocation, efficient data sharing between functions, and the construction of fundamental data structures like linked lists and trees. While powerful, raw pointers carry significant risks including dangling pointers, memory leaks, and undefined behavior from uninitialized or null dereferencing, which is why modern C++ increasingly favors safer alternatives like smart pointers and references for everyday use. Mastering pointers and understanding exactly how memory addressing and dereferencing work remains essential for truly understanding how C++ programs operate under the hood.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.