Docs
README
Pointers in C++
Table of Contents
- •What is a Pointer?
- •Pointer Declaration and Initialization
- •Pointer Operators
- •Pointer Arithmetic
- •Pointers and Arrays
- •Pointers to Pointers
- •const with Pointers
- •Common Pitfalls
- •Best Practices
What is a Pointer?
A pointer is a variable that stores the memory address of another variable.
Memory Visualization
Variable: int x = 42;
Memory: [ 42 ]
Address: 0x7fff5fbff8ac
↑
Pointer: int* ptr = &x;
ptr stores 0x7fff5fbff8ac
Why Use Pointers?
- •Efficiency: Pass large objects without copying
- •Dynamic memory: Allocate memory at runtime
- •Data structures: Build linked lists, trees, graphs
- •Polymorphism: Enable runtime behavior selection
- •System programming: Direct memory access
Pointer Declaration and Initialization
Declaration Syntax
type* pointerName; // Preferred style
type *pointerName; // Also valid
type * pointerName; // Also valid
Examples
int* intPtr; // Pointer to int
double* doublePtr; // Pointer to double
char* charPtr; // Pointer to char
string* stringPtr; // Pointer to string
// Multiple pointers (careful with syntax!)
int *p1, *p2; // Both are pointers
int* p3, p4; // p3 is pointer, p4 is int!
Initialization
int x = 10;
int* ptr = &x; // Initialize with address of x
int* ptr2 = nullptr; // Initialize to null (C++11)
int* ptr3 = NULL; // Old style (avoid)
int* ptr4 = 0; // Also null (avoid)
nullptr (C++11)
Always use nullptr instead of NULL or 0:
int* ptr = nullptr;
if (ptr == nullptr) {
cout << "Pointer is null" << endl;
}
// Can use in boolean context
if (!ptr) {
cout << "Pointer is null" << endl;
}
Pointer Operators
Address-of Operator &
Gets the memory address of a variable.
int x = 42;
int* ptr = &x; // ptr holds address of x
cout << "Value of x: " << x << endl; // 42
cout << "Address of x: " << &x << endl; // 0x7fff...
cout << "Value of ptr: " << ptr << endl; // 0x7fff... (same)
Dereference Operator *
Accesses the value at the address stored in pointer.
int x = 42;
int* ptr = &x;
cout << *ptr << endl; // 42 (value at address)
*ptr = 100; // Modify value through pointer
cout << x << endl; // 100 (x changed!)
Visualizing the Relationship
int x = 42;
int* ptr = &x;
x ptr
[42] → [0x1000]
0x1000 0x2000
&x returns 0x1000
*ptr returns 42
&ptr returns 0x2000
Pointer Arithmetic
Pointers can be incremented/decremented to traverse memory.
Basic Arithmetic
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // Points to arr[0]
cout << *ptr << endl; // 10
ptr++; // Move to next int
cout << *ptr << endl; // 20
ptr += 2; // Skip 2 ints
cout << *ptr << endl; // 40
ptr--; // Move back
cout << *ptr << endl; // 30
How Pointer Arithmetic Works
Pointer arithmetic accounts for the size of the data type:
int* intPtr; // ++intPtr moves by sizeof(int) = 4 bytes
char* charPtr; // ++charPtr moves by sizeof(char) = 1 byte
double* dblPtr; // ++dblPtr moves by sizeof(double) = 8 bytes
Pointer Subtraction
int arr[] = {10, 20, 30, 40, 50};
int* start = arr;
int* end = arr + 4;
ptrdiff_t diff = end - start; // 4 (number of elements)
Comparison
int arr[] = {10, 20, 30};
int* p1 = arr;
int* p2 = arr + 2;
if (p1 < p2) {
cout << "p1 comes before p2" << endl;
}
Pointers and Arrays
Array-Pointer Relationship
Array names decay to pointers to their first element.
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr; // arr decays to &arr[0]
// Equivalent to:
int* ptr2 = &arr[0];
cout << *ptr << endl; // 1 (first element)
cout << arr[0] << endl; // 1 (same)
cout << ptr[0] << endl; // 1 (same - bracket notation works!)
Accessing Array Elements
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr;
// These are all equivalent:
arr[2] // 30
*(arr + 2) // 30
ptr[2] // 30
*(ptr + 2) // 30
2[arr] // 30 (weird but valid!)
Difference: Array vs Pointer
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
sizeof(arr); // 20 (5 * sizeof(int))
sizeof(ptr); // 8 (size of pointer on 64-bit)
arr++; // ERROR: can't modify array name
ptr++; // OK: pointer can be modified
Passing Arrays to Functions
// These declarations are equivalent for parameters:
void func(int arr[]);
void func(int* arr);
void func(int arr[10]); // Size is ignored!
// Always pass size separately:
void printArray(int* arr, int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
}
Pointers to Pointers
A pointer can point to another pointer.
Declaration and Usage
int x = 10;
int* ptr = &x; // Pointer to int
int** pptr = &ptr; // Pointer to pointer to int
cout << x << endl; // 10
cout << *ptr << endl; // 10
cout << **pptr << endl; // 10
cout << &x << endl; // Address of x
cout << ptr << endl; // Same address
cout << *pptr << endl; // Same address
cout << &ptr << endl; // Address of ptr
cout << pptr << endl; // Same address
Visual Representation
x ptr pptr
[ 10 ] → [0x1000] → [0x2000]
0x1000 0x2000 0x3000
*ptr = 10
*pptr = 0x1000 (address of x)
**pptr = 10
Use Case: Modifying Pointers in Functions
void allocate(int** pptr) {
*pptr = new int(42); // Modify the original pointer
}
int main() {
int* ptr = nullptr;
allocate(&ptr); // Pass address of pointer
cout << *ptr << endl; // 42
delete ptr;
}
const with Pointers
Four Combinations
| Declaration | Pointer | Data | Description |
|---|---|---|---|
int* ptr | Mutable | Mutable | Can change both |
const int* ptr | Mutable | Const | Can't change data |
int* const ptr | Const | Mutable | Can't change pointer |
const int* const ptr | Const | Const | Can't change either |
Pointer to Const (Read from Right to Left)
int x = 10;
int y = 20;
const int* ptr = &x; // Pointer to const int
// Read: ptr is a pointer to int that is const
*ptr = 100; // ERROR: can't modify data
ptr = &y; // OK: can change what ptr points to
Const Pointer
int x = 10;
int y = 20;
int* const ptr = &x; // Const pointer to int
// Read: ptr is a const pointer to int
*ptr = 100; // OK: can modify data
ptr = &y; // ERROR: can't change pointer
Const Pointer to Const
int x = 10;
int y = 20;
const int* const ptr = &x;
*ptr = 100; // ERROR: can't modify data
ptr = &y; // ERROR: can't change pointer
Reading Trick
Read declarations from right to left:
const int* ptr; // ptr is a (*) pointer to (int) that is (const)
int* const ptr; // ptr is a (const) constant (*) pointer to (int)
const int* const ptr; // ptr is a (const) (*) pointer to (int) that is (const)
Common Pitfalls
1. Uninitialized Pointers
int* ptr; // Uninitialized - contains garbage!
*ptr = 10; // UNDEFINED BEHAVIOR - crash likely
// Fix: Always initialize
int* ptr = nullptr;
2. Dangling Pointers
int* createDangling() {
int x = 10;
return &x; // x is destroyed when function returns!
}
int* ptr = createDangling(); // Dangling pointer!
*ptr = 20; // UNDEFINED BEHAVIOR
3. Memory Leaks
void leak() {
int* ptr = new int(42);
// Forgot to delete!
} // ptr goes out of scope, memory is lost
4. Double Deletion
int* ptr = new int(42);
delete ptr;
delete ptr; // UNDEFINED BEHAVIOR - double delete!
// Fix: Set to nullptr after delete
delete ptr;
ptr = nullptr;
5. Using Deleted Memory
int* ptr = new int(42);
delete ptr;
cout << *ptr << endl; // UNDEFINED BEHAVIOR - use after free
Best Practices
✅ Do
// Always initialize pointers
int* ptr = nullptr;
// Check for null before dereferencing
if (ptr != nullptr) {
cout << *ptr << endl;
}
// Use nullptr instead of NULL or 0
int* ptr = nullptr;
// Set to nullptr after delete
delete ptr;
ptr = nullptr;
// Prefer references when possible
void modifyValue(int& value); // Safer than pointer
// Use smart pointers for ownership
unique_ptr<int> ptr = make_unique<int>(42);
❌ Don't
// Don't use uninitialized pointers
int* ptr; // Garbage value!
// Don't return addresses of local variables
int* bad() {
int x = 10;
return &x; // Dangling!
}
// Don't forget to check for null
*ptr = 10; // Crash if null!
// Don't use raw pointers for ownership
int* ptr = new int(42); // Use smart pointers instead
Quick Reference
// Declaration
int* ptr = nullptr; // Pointer to int
// Operators
&x // Address of x
*ptr // Value at ptr's address
// Initialization
int x = 10;
int* ptr = &x; // Points to x
// Access
*ptr = 20; // Modify through pointer
cout << *ptr; // Read through pointer
// Pointer arithmetic
ptr++ // Next element
ptr + n // n elements ahead
ptr1 - ptr2 // Distance between pointers
// Arrays
int arr[] = {1, 2, 3};
int* ptr = arr; // Points to arr[0]
ptr[i] == *(ptr + i) // Equivalent access
// Const
const int* ptr; // Can't modify data
int* const ptr; // Can't modify pointer
const int* const ptr; // Can't modify either
// Null check
if (ptr != nullptr) { }
if (ptr) { } // Same thing
Compile & Run
g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples