Notifications

No notifications

/Phase 3

Pointers in C

Variables That Hold Addresses 📍

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 age

printf("%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

Two Operators You Must Know

OpNameMeaning
&address-ofGive me the address of this variable
*dereferenceGo to that address, read/write the value

Declaring a Pointer

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 pointers

Reading and Writing Through a Pointer

int x = 10;
int *p = &x;

*p = 50; // write through p → x is now 50 printf("%d\n", x); // 50

NULL — The "Points to Nothing" Value

int *p = NULL;     // explicitly "no address yet"
if (p != NULL) {
    printf("%d", *p);
}

> ⚠️ Dereferencing a NULL or uninitialised pointer = crash (segfault).

On this page

Detailed Theory

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.

Memory Is a Big Numbered Array

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."

& and * Are Inverses

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"

Why Pointers Exist — Three Reasons

1. Modifying a Variable in Another Function

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

2. Avoiding Expensive Copies

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.")

3. Building Dynamic Data Structures

Linked lists, trees, hash tables, growable arrays — all built from pointers.

Pointer Types Matter

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 byte

Pointer Arithmetic

int 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.

Arrays vs Pointers — Subtle Difference

int  a[5];
int *p;

Array aPointer p
What it isA block of 5 intsA single address
sizeof20 bytes8 bytes
Can be reassigned?a = … is illegalp = q; works
Decays to pointer?Yes, in most contextsN/A

When you pass a to a function, it decays into a int * — that's why functions can't tell array length.

NULL — The Safe "No Address"

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.

The Three Deadly Sins of Pointers

1. Dangling Pointer

Pointer to memory that's already been freed or reclaimed.

int *p;
{
    int x = 5;
    p = &x;       // p points into local scope
}                  // x dies here
*p;                // ❌ dangling

2. Wild Pointer

Uninitialised pointer holding garbage.

int *p;            // value is GARBAGE
*p = 10;           // ❌ writes to a random address

3. Memory Leak

Pointer to allocated memory is lost without freeing.

int *p = malloc(40);
p = malloc(80);    // ❌ first 40 bytes are now unreachable

Pointer to Pointer

A 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 With Pointers — Two Places to Put It

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;  // both

Read 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"

Pointers Cheat-Sheet

Want to...Do
Get the address of x&x
Read the value at address p*p
Modify caller's intPass &x, take int *
Step through an arrayfor (int *p = a; p < a + n; p++) *p = ...
Mark "no address yet"p = NULL
Avoid modifying through pointerconst T *p

Once pointers click, you understand C.