Last 30 Days
No notifications
A pointer is a variable whose value is the memory address of another variable. Pointers are what make C powerful — and what make it dangerous.
int age = 19; // a normal int
int *p = &age; // p stores the ADDRESS of ageprintf("%d\n", age); // 19 — the value
printf("%p\n", &age); // 0x7ffe... — the address
printf("%p\n", p); // same address
printf("%d\n", *p); // 19 — go to that address, read int
| Op | Name | Meaning |
& | address-of | Give me the address of this variable |
* | dereference | Go to that address, read/write the value |
int *p; // pointer to int
double *q; // pointer to double
char *s; // pointer to char (often a "string")
void *v; // pointer to anything (no type info)The * belongs to the variable, not the type:
int *a, b; // a is int*, b is plain int (gotcha!)
int *a, *b; // both are pointersint x = 10;
int *p = &x;*p = 50; // write through p → x is now 50
printf("%d\n", x); // 50
int *p = NULL; // explicitly "no address yet"
if (p != NULL) {
printf("%d", *p);
}> ⚠️ Dereferencing a NULL or uninitialised pointer = crash (segfault).
Pointers terrify beginners — but they're just integers with a special interpretation: "the value of this variable is the location of another variable."
Once you internalise that, everything else (arrays, strings, dynamic memory, linked lists, function pointers) is just patterns built on top.
Imagine RAM as a giant array of bytes, each with an address:
Address: 1000 1001 1002 1003 1004 1005 ...
Byte: [42] [00] [00] [00] [37] [00] ...When you write int x = 42;, the compiler picks an address (say 1000) and writes 42 there in 4 bytes (it's an int).
A pointer int *p = &x; is a variable whose value is the number 1000. It's interpreted as "the place where an int lives."
int x = 5;
int *p = &x;*&x == x // taking the address then dereferencing → original
&*p == p // dereferencing then taking the address → original
Mental model:
& = "the address of"* = "the thing at"*p means "the int at address p"
&x means "the address where x lives"We saw this with swap:
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}int x = 1, y = 2;
swap(&x, &y); // pass the addresses, not the values
Passing a 1-MB struct by value copies 1 MB. Passing a pointer to it copies 8 bytes:
void process(const Image *img) { ... } // 8 bytes copied(const says "I won't modify what it points to.")
Linked lists, trees, hash tables, growable arrays — all built from pointers.
Even though all pointers are just addresses, the type tells the compiler:
1. How many bytes to read/write when you dereference.
2. How much to advance for pointer arithmetic (p + 1).
int *pi; // *pi reads 4 bytes, pi+1 jumps 4 bytes
double *pd; // *pd reads 8 bytes, pd+1 jumps 8 bytes
char *pc; // *pc reads 1 byte, pc+1 jumps 1 byteint arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p points to arr[0]printf("%d\n", *p); // 10
printf("%d\n", *(p + 1)); // 20 — same as arr[1]
printf("%d\n", *(p + 2)); // 30 — same as arr[2]
In fact, arr[i] is just shorthand for *(arr + i) in C. They are exactly the same. Even 5[arr] works (because *(5 + arr) == *(arr + 5)).
> ⚠️ Pointer arithmetic only makes sense inside an array. Going outside is undefined behaviour.
int a[5];
int *p;Array a | Pointer p | |
| What it is | A block of 5 ints | A single address |
sizeof | 20 bytes | 8 bytes |
| Can be reassigned? | ❌ a = … is illegal | ✅ p = q; works |
| Decays to pointer? | Yes, in most contexts | N/A |
When you pass a to a function, it decays into a int * — that's why functions can't tell array length.
Always initialise pointers. An uninitialised pointer holds garbage and dereferencing it is undefined behaviour. Use NULL to mean "not pointing anywhere yet":
int *p = NULL;if (p != NULL) {
/* safe to use *p */
}
The defensive habit: check for NULL before dereferencing.
int *p;
{
int x = 5;
p = &x; // p points into local scope
} // x dies here
*p; // ❌ danglingint *p; // value is GARBAGE
*p = 10; // ❌ writes to a random addressint *p = malloc(40);
p = malloc(80); // ❌ first 40 bytes are now unreachableA pointer is itself a variable, so it has an address — meaning you can have a pointer to a pointer:
int x = 5;
int *p = &x;
int **pp = &p;printf("%d\n", **pp); // 5
Used for: arrays of strings (char *argv[] in main), modifying a pointer inside a function, dynamic 2-D arrays.
const int *p; // pointer to const int — *p can't change, p can move
int *const p; // const pointer — p can't move, *p can change
const int *const p; // bothRead right-to-left:
const int *p → "p is a pointer to a constant int"int *const p → "p is a constant pointer to an int"| Want to... | Do |
Get the address of x | &x |
Read the value at address p | *p |
| Modify caller's int | Pass &x, take int * |
| Step through an array | for (int *p = a; p < a + n; p++) *p = ... |
| Mark "no address yet" | p = NULL |
| Avoid modifying through pointer | const T *p |
Once pointers click, you understand C.