Pointers are the single concept that separates programmers who understand C from programmers who copy code until it works. Once you see what is actually happening in memory, everything clicks. This is that explanation.
What a Variable Actually Is
Every variable in your program occupies a location in memory. That location has an address — a number that identifies exactly where in RAM the data lives.
#include <stdio.h>
int main() {
int x = 42;
printf("Value of x: %dn", x);
printf("Address of x: %pn", (void*)&x);
return 0;
}
Output (addresses vary per run):
Value of x: 42
Address of x: 0x7ffee4b2c8ac
Memory:
Address Value
0x7ffee4b2c8ac [ 42 ] ← x lives here
The & operator gives you the address of a variable. That address is just a number.
What a Pointer Is
A pointer is a variable that stores an address.
#include <stdio.h>
int main() {
int x = 42;
int *p = &x; /* p holds the address of x */
printf("x = %dn", x);
printf("&x = %pn", (void*)&x);
printf("p = %pn", (void*)p); /* same as &x */
printf("*p = %dn", *p); /* value at the address p holds */
return 0;
}
Memory:
Address Value
0x7ffee4b2c8ac [ 42 ] ← x
0x7ffee4b2c8b0 [ 0x7ffee4b2c8ac ] ← p (stores the address of x)
*p is called dereferencing — it follows the address stored in p and reads the value at that location. *p and x both give you 42.
Modifying a Variable Through a Pointer
#include <stdio.h>
int main() {
int x = 42;
int *p = &x;
*p = 100; /* write 100 to the address stored in p */
printf("x = %dn", x); /* prints 100 — x was modified through p */
return 0;
}
This is what makes pointers powerful. A function can receive a pointer and modify the original variable in the caller — not a copy of it.
Pointers as Function Parameters
C passes all arguments by value. Without pointers, a function cannot modify the caller’s variables:
#include <stdio.h>
void add_ten_wrong(int n) {
n += 10; /* modifies the local copy only */
}
void add_ten_right(int *n) {
*n += 10; /* modifies the value at the address n points to */
}
int main() {
int x = 5;
add_ten_wrong(x);
printf("After wrong: %dn", x); /* still 5 */
add_ten_right(&x);
printf("After right: %dn", x); /* 15 */
return 0;
}
Pointer Arithmetic
You can add integers to pointers. The increment is scaled by the size of the pointed-to type.
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; /* p points to arr[0] */
printf("*p = %dn", *p); /* 10 */
printf("*(p + 1) = %dn", *(p+1)); /* 20 */
printf("*(p + 2) = %dn", *(p+2)); /* 30 */
/* incrementing the pointer */
p++;
printf("after p++: *p = %dn", *p); /* 20 */
return 0;
}
Memory (assuming 4-byte ints):
Address Value
1000 [ 10 ] ← arr[0], p initially points here
1004 [ 20 ] ← arr[1], p+1 points here
1008 [ 30 ] ← arr[2], p+2 points here
Adding 1 to an int* moves forward by 4 bytes (the size of int). Adding 1 to a char* moves forward by 1 byte. The compiler handles the multiplication automatically.
Double Pointers (Pointer to a Pointer)
#include <stdio.h>
int main() {
int x = 42;
int *p = &x;
int **pp = &p; /* pp holds the address of p */
printf("x = %dn", x);
printf("*p = %dn", *p);
printf("**pp = %dn", **pp); /* same as x */
**pp = 99;
printf("x after **pp = 99: %dn", x); /* 99 */
return 0;
}
Memory:
Address Value
1000 [ 42 ] ← x
1008 [ 1000 ] ← p (points to x)
1016 [ 1008 ] ← pp (points to p)
Double pointers are used when a function needs to modify a pointer in the caller — for example, a function that allocates memory and updates the caller’s pointer.
NULL — the Zero Address
int *p = NULL; /* p holds address 0 — points to nothing */
if (p == NULL) {
printf("p is not pointing to anythingn");
}
/* Never dereference NULL */
*p = 42; /* segfault — address 0 is not accessible */
NULL is a convention for “this pointer is not valid.” Always initialize pointers to NULL when you do not have a valid address yet, and always check before dereferencing.
Common Pointer Mistakes
/* 1. Uninitialized pointer — points to garbage address */
int *p;
*p = 42; /* undefined behaviour */
/* 2. Forgetting & when calling a function */
int x = 5;
scanf("%d", x); /* wrong — passing the value */
scanf("%d", &x); /* right — passing the address */
/* 3. Pointer type mismatch */
float f = 3.14;
int *p = &f; /* wrong type — reading through p gives garbage */
TL;DR
- A pointer stores a memory address, not a value
&xgives the address of x — use this to create a pointer to x*pdereferences a pointer — reads or writes the value at the stored address- Pointer arithmetic is scaled by the size of the pointed-to type
- Always initialize pointers; check for NULL before dereferencing
- Double pointers (
int**) let functions modify the caller’s pointer - Once you are comfortable with these basics, explore function pointers — pointers that store the address of a function instead of data