Skillversitas

Pointers in C++

A pointer is a variable that stores the memory address of another variable. Instead of storing a value directly, a pointer "points" to the location in memory where the data is stored. Pointers are one of the most powerful — and most dangerous — features in C++.

Pointers are needed for: dynamic memory allocation (new/delete), passing data to functions without copying (pass by reference), building data structures such as linked lists and trees, and interacting directly with hardware.

Every pointer has a type that indicates the type of data it points to. A pointer of type int* points to an integer, double* to a double, and so on. The size of a pointer itself depends on the system architecture (4 bytes on 32-bit, 8 bytes on 64-bit).

  • & (address-of): Gets the memory address of a variable.
  • * (dereference): Gets the value pointed to by a pointer.
  • -> (arrow): Accesses a struct/class member through a pointer.

Null Pointer

nullptr (C++11) is the literal for a pointer that points to nothing. Before C++11, programmers used NULL or 0. Always initialize a pointer to nullptr when its value is not yet known, to avoid dangling pointers.

Code Example

#include <iostream>
using namespace std;

struct Student {
    string name;
    int score;
};

void swapValues(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void print(Student* s) {
    cout << "Name: " << s->name << ", Score: " << s->score << endl;
}

int main() {
    // Pointer declaration
    int x = 10;
    int* ptr = &x; // ptr stores the address of x

    cout << "Value of x: " << x << endl;
    cout << "Address of x: " << &x << endl;
    cout << "ptr value (address of x): " << ptr << endl;
    cout << "Value pointed to by ptr: " << *ptr << endl;

    // Changing a value through a pointer
    *ptr = 25;
    cout << "\nAfter *ptr = 25:" << endl;
    cout << "x = " << x << endl;
    cout << "*ptr = " << *ptr << endl;

    // Null pointer
    int* nullPtr = nullptr;
    if (nullPtr == nullptr) {
        cout << "\nNull pointer!" << endl;
    }

    // Pointer arithmetic
    int arr[] = {10, 20, 30, 40, 50};
    int* pArr = arr; // array decay to pointer

    cout << "\nPointer arithmetic:" << endl;
    for (int i = 0; i < 5; i++) {
        cout << "pArr[" << i << "] = " << *(pArr + i) << " (address: " << (pArr + i) << ")" << endl;
    }

    // Pointer to pointer
    int val = 42;
    int* p1 = &val;
    int** p2 = &p1;
    cout << "\nPointer to pointer:" << endl;
    cout << "val = " << val << endl;
    cout << "*p1 = " << *p1 << endl;
    cout << "**p2 = " << **p2 << endl;

    // Pointers and arrays
    cout << "\nArray through pointer:" << endl;
    for (int* p = arr; p < arr + 5; p++) {
        cout << *p << " ";
    }
    cout << endl;

    // Void pointer (can point to any type)
    int valInt = 100;
    float valFloat = 3.14f;
    void* vptr;

    vptr = &valInt;
    cout << "\nVoid ptr to int: " << *(static_cast<int*>(vptr)) << endl;

    vptr = &valFloat;
    cout << "Void ptr to float: " << *(static_cast<float*>(vptr)) << endl;

    // Pointer to struct
    Student student1 = {"Mike", 92};
    Student* ptrStudent = &student1;
    print(ptrStudent);
    ptrStudent->score = 95;
    cout << "After modification: ";
    print(ptrStudent);

    // Swap function using pointers
    int a = 10, b = 20;
    cout << "\nBefore swap: a=" << a << ", b=" << b << endl;
    swapValues(&a, &b);
    cout << "After swap: a=" << a << ", b=" << b << endl;

    // Dynamic allocation
    int* dynArr = new int[5];
    for (int i = 0; i < 5; i++) {
        dynArr[i] = (i + 1) * 10;
    }
    cout << "\nDynamic array: ";
    for (int i = 0; i < 5; i++) {
        cout << dynArr[i] << " ";
    }
    cout << endl;
    delete[] dynArr;
    dynArr = nullptr;

    // Dangling pointer (avoid!)
    int* dangling;
    {
        int local = 42;
        dangling = &local;
    }
    // local is out of scope, dangling is now invalid
    // cout << *dangling; // UNDEFINED BEHAVIOR - don't do this!

    return 0;
}

Code Explanation

  • int* ptr = &x; — Declares a pointer ptr of type int*, initialized with the address of x. The & operator gets the memory address.
  • *ptr — Dereference: gets the value stored at the address ptr points to. If ptr points to x, then *ptr has the same value as x.
  • *ptr = 25; — Changes the value at the pointed-to address. Since ptr points to x, x becomes 25.
  • int* nullPtr = nullptr; — A pointer that points to nothing. Always check for nullptr before dereferencing to avoid a crash.
  • *(pArr + i) — Pointer arithmetic. pArr + i moves forward by i elements (not i bytes). * gets the value at that position.
  • int** p2 = &p1; — A pointer to a pointer. p2 points to p1, which points to val. **p2 accesses the value of val through two levels of dereferencing.
  • pArr++ or pArr + 1 — The pointer moves by the size of the data type. For an int*, incrementing moves 4 bytes (or 8 on 64-bit).
  • void* vptr — A universal pointer. It can point to any data type but must be cast back to the original type before dereferencing. Used in generic APIs.
  • ptrStudent->name — The -> operator for accessing a struct member through a pointer. Equivalent to (*ptrStudent).name.
  • swapValues(&a, &b) — Passes the addresses of a and b to the function. The function can modify the original values because it receives their addresses.
  • new int[5] — Dynamically allocates 5 integers on the heap. It must be deallocated with delete[] to prevent a memory leak.
  • delete[] dynArr; dynArr = nullptr; — Deallocates the dynamic array, then sets the pointer to nullptr to avoid a dangling pointer.
  • Dangling pointer block — After local leaves its scope, dangling holds an address that is no longer valid. Dereferencing it is undefined behavior.

Everyday Use Cases

Dynamic data: When a program needs to determine the amount of data at runtime (for example, the number of students registering is not known in advance), pointers with new allow allocating memory as needed.

Swap function: In sorting algorithms, pointers allow a swapValues() function to modify the values of variables outside the function's scope. Without pointers (or references), a function can only copy its parameters.

Other examples:
- Linked list: Each node stores a pointer to the next node, allowing data to be added without contiguous memory allocation.
- Polymorphism: A base class pointer to a derived class (Base* ptr = new Derived()) enables virtual function calls.
- Callback function: A pointer to a function (void (*callback)(int)) allows passing a function as a parameter.

Conclusion

Pointers are memory-address-storing variables that are extremely powerful in C++. They enable dynamic allocation, pass by reference, and advanced data structures. However, pointers are also dangerous if not used correctly — dangling pointers, memory leaks, and null dereferences are common mistakes. Always initialize pointers to nullptr, deallocate memory you allocate, and avoid complex pointer arithmetic unless it is truly necessary.