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).
Pointer-Related Operators
&(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 pointerptrof typeint*, initialized with the address ofx. The&operator gets the memory address.*ptr— Dereference: gets the value stored at the addressptrpoints to. Ifptrpoints tox, then*ptrhas the same value asx.*ptr = 25;— Changes the value at the pointed-to address. Sinceptrpoints tox,xbecomes 25.int* nullPtr = nullptr;— A pointer that points to nothing. Always check fornullptrbefore dereferencing to avoid a crash.*(pArr + i)— Pointer arithmetic.pArr + imoves forward byielements (notibytes).*gets the value at that position.int** p2 = &p1;— A pointer to a pointer.p2points top1, which points toval.**p2accesses the value ofvalthrough two levels of dereferencing.pArr++orpArr + 1— The pointer moves by the size of the data type. For anint*, 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 ofaandbto 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 withdelete[]to prevent a memory leak.delete[] dynArr; dynArr = nullptr;— Deallocates the dynamic array, then sets the pointer tonullptrto avoid a dangling pointer.- Dangling pointer block — After
localleaves its scope,danglingholds 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.