Skip to content

Pointers

Pointers are one of the most powerful and dangerous features of C++. This chapter provides an in-depth look at pointers, their types, and how to use them safely.

A pointer is a variable that stores the memory address of another variable:

┌─────────────────────────────────────────────────────────────┐
│ Pointer Concept │
├─────────────────────────────────────────────────────────────┤
│ │
│ int variable: int* pointer: │
│ ┌─────────┐ ┌─────────┐ │
│ │ 42 │ │ 0x1000 │──────┐ │
│ └─────────┘ └─────────┘ │ │
│ 0x1000 │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 42 │◄───┘ (int) │ │
│ └─────────┘ │
│ Memory: 0x1000 │
│ │
└─────────────────────────────────────────────────────────────┘
#include <iostream>
int main() {
int num = 42;
// Declare pointer to int
int* ptr = &num; // & gets the address
// Print values
std::cout << "Value: " << num << "\n";
std::cout << "Address: " << &num << "\n";
std::cout << "Pointer value: " << ptr << "\n";
std::cout << "Dereferenced: " << *ptr << "\n"; // * dereferences
return 0;
}
int* intPtr; // Pointer to int
double* doublePtr; // Pointer to double
char* charPtr; // Pointer to char
void* voidPtr; // Pointer to anything (raw)
int value = 42;
// Pointer to const
const int* ptr1 = &value;
// *ptr1 = 100; // Error: can't modify through ptr1
ptr1 = nullptr; // OK: can change pointer
// Const pointer
int* const ptr2 = &value;
*ptr2 = 100; // OK: can modify value
// ptr2 = nullptr; // Error: can't change pointer
// Const pointer to const
const int* const ptr3 = &value;
// Both pointer and value are const
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // Points to arr[0]
// Pointer arithmetic
ptr++; // Now points to arr[1]
ptr += 2; // Now points to arr[3]
// Access element
int val = *(ptr + 1); // arr[4]
int val2 = *ptr; // arr[3]
// Difference
ptrdiff_t diff = &arr[4] - &arr[0]; // 4

Arrays and pointers are closely related:

int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr; // Array decays to pointer
// Both work the same
arr[0] == *(arr + 0) == *ptr == ptr[0]
// Passing arrays to functions
void processArray(int* arr, size_t size) {}
// Both declarations equivalent
void processArray(int arr[], size_t size) {}
void processArray(int* arr, size_t size) {}
class Person {
private:
std::string name;
public:
Person(const std::string& n) : name(n) {}
void greet() { std::cout << "Hello, I'm " << name << "\n"; }
};
int main() {
Person obj("Alice");
Person* ptr = &obj;
// Access members through pointer
ptr->greet(); // Same as (*ptr).greet()
}
#include <cstdlib>
int main() {
// Allocate single int
int* ptr = new int(42);
std::cout << *ptr << "\n";
delete ptr; // Free memory
ptr = nullptr; // Avoid dangling pointer
// Allocate array
int* arr = new int[10];
delete[] arr; // Free array
// Modern C++ prefers smart pointers
}

A dangling pointer points to freed memory:

int* ptr = new int(42);
delete ptr;
// ptr is now dangling - don't use it!
// Set to null after delete
ptr = nullptr;
// Check before using
if (ptr != nullptr) {
// Safe to use
}
int* nullPtr = nullptr; // C++11 nullptr
// Check null
if (ptr != nullptr) {}
// Use in conditions (converts to false)
if (ptr) {} // False if null
int value = 42;
int* ptr1 = &value;
int** ptr2 = &ptr1; // Pointer to pointer
**ptr2 = 100; // Changes value to 100
#include <iostream>
class DynamicArray {
private:
int* data;
size_t size;
public:
DynamicArray(size_t s) : size(s) {
data = new int[size](); // Value-initialized to 0
}
~DynamicArray() {
delete[] data; // Free memory
}
int& operator[](size_t index) {
return data[index];
}
size_t getSize() const { return size; }
// Prevent copying (Rule of 5)
DynamicArray(const DynamicArray&) = delete;
DynamicArray& operator=(const DynamicArray&) = delete;
};
int main() {
DynamicArray arr(5);
for (size_t i = 0; i < arr.getSize(); i++) {
arr[i] = (i + 1) * 10;
}
for (size_t i = 0; i < arr.getSize(); i++) {
std::cout << arr[i] << " ";
}
// Output: 10 20 30 40 50
return 0;
}
// 1. Not initializing
int* ptr; // Uninitialized - garbage value!
// 2. Memory leak
int* ptr = new int(42);
// forgot delete
// 3. Dangling pointer
int* ptr = new int(42);
delete ptr;
std::cout << *ptr; // Undefined behavior!
// 4. Double delete
int* ptr = new int(42);
delete ptr;
delete ptr; // Undefined behavior!
// 5. Wrong delete
int* ptr = new int[10];
delete ptr; // Wrong! Should be delete[]
  1. Prefer smart pointers over raw pointers
  2. Always initialize pointers
  3. Check for null before dereferencing
  4. Set pointer to null after delete
  5. Match new with delete, new[] with delete[]
  • Pointers store memory addresses
  • Use * to dereference pointers
  • Use & to get address of a variable
  • Pointer arithmetic works on array elements
  • Always delete dynamically allocated memory
  • Prefer smart pointers in modern C++

Let’s learn about references and how they differ from pointers.

Next Chapter: 20_references.md - References and Lifetime