Docs

README

Pointers in C++

Table of Contents

  1. What is a Pointer?
  2. Pointer Declaration and Initialization
  3. Pointer Operators
  4. Pointer Arithmetic
  5. Pointers and Arrays
  6. Pointers to Pointers
  7. const with Pointers
  8. Common Pitfalls
  9. 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

DeclarationPointerDataDescription
int* ptrMutableMutableCan change both
const int* ptrMutableConstCan't change data
int* const ptrConstMutableCan't change pointer
const int* const ptrConstConstCan'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
README - C++ Tutorial | DeepML