CppPointers

Pointers

A pointer is a variable whose value is a memory address. Instead of holding data directly, a pointer holds the location of where that data lives. Pointers are one of the defining features of C and C++ — they enable dynamic memory allocation, efficient passing of large data, building linked data structures, and direct hardware access. They are also one of the most common sources of bugs in the entire language, so precision matters here more than almost anywhere else in C++.

Declaring and initializing a pointer
A pointer is declared with an asterisk (*) after the type, indicating “pointer to this type.” The address-of operator (&) retrieves the memory address of an existing variable, which can then be stored in a pointer.

pointer_basics.cpp

CPP
#include <iostream>

int main() {
    int x = 42;
    int* p = &x;    // p holds the address of x

    std::cout << "Value of x:        " << x << std::endl;
    std::cout << "Address of x:      " << &x << std::endl;
    std::cout << "Value stored in p: " << p << std::endl;   // same address

    return 0;
}
Dereferencing a pointer
The dereference operator, also written as * but used in an expression rather than a declaration, accesses the value stored at the address a pointer holds.

dereference.cpp

CPP
#include <iostream>

int main() {
    int x = 42;
    int* p = &x;

    std::cout << *p << std::endl;   // 42 — the value x holds

    *p = 100;                        // writes through the pointer
    std::cout << x << std::endl;     // 100 — x itself changed!

    return 0;
}
Null pointers

A pointer that doesn't point to any valid object should be set to a null value, so that code can check for it before dereferencing.

nullptr_basics.cpp

CPP
int* p = nullptr;   // modern C++ (C++11 and later)

if (p == nullptr) {
    std::cout << "p does not point to anything yet" << std::endl;
}

// *p;   // dereferencing a null pointer is undefined behavior — usually a crash
Always use nullptr, never NULL or 0, in new code
Older C and C++ code uses NULL (a macro, usually just 0) or a bare 0 to represent a null pointer. Both are ambiguous: they can be confused with the integer zero, which causes real problems with function overload resolution and templates. nullptr, introduced in C++11, has its own type (std::nullptr_t) and can only ever mean “a null pointer,” removing the ambiguity entirely. There is no reason to use NULL or 0 for pointers in modern code.
Pointer to pointer

Because a pointer is itself a variable stored somewhere in memory, you can take its address too, producing a pointer to a pointer. This shows up occasionally — for example, when a function needs to modify what a caller's pointer points to.

pointer_to_pointer.cpp

CPP
int x = 10;
int* p = &x;
int** pp = &p;    // pp holds the address of p

std::cout << **pp << std::endl;   // 10 — dereference twice to reach x
Why pointers are powerful but dangerous
Pointers give you direct control over memory, which is exactly why they are risky. A pointer can be left uninitialized (pointing at random garbage), can become a dangling pointer (pointing at memory that has already been freed), or can simply be “wild” (never pointed anywhere valid to begin with). Dereferencing any of these is undefined behavior. The chapters ahead on pointer arithmetic, dynamic memory, memory leaks, and smart pointers walk through these dangers in detail — and how modern C++ avoids most of them.
  • A pointer stores a memory address; & retrieves an address, * dereferences a pointer.

  • Always initialize pointers — either to a valid address or to nullptr.

  • Never use NULL or 0 for null pointers in new code — use nullptr.

  • Dereferencing an uninitialized, null, or dangling pointer is undefined behavior.

Coming up
Next: pointer arithmetic, then a direct pointer-vs-reference comparison, followed by dynamic memory management and the modern smart pointer types that make manual pointer bookkeeping far less necessary.