Pointers Progress
0%
Deep Dive  ·  Memory & Addresses

C Pointers — Complete Guide

From first principles to common bugs — understand pointers deeply, including &, *, dereferencing, pointer arithmetic, and the 5 mistakes every beginner makes.

What is a pointer
& and * explained
Dereferencing
Pointer arithmetic
5 common bugs
1

What is a Pointer?

0 – 10 min

Every variable lives somewhere in RAM. Your computer's memory is like a long row of numbered boxes — each box has an address (a number) and holds a value. A pointer is a variable whose job is to store one of those address numbers — not a value itself, but where a value lives.

  • Regular variable: holds a value — int x = 42; → box contains 42
  • Pointer variable: holds an address — int *p = &x; → box contains the location of x

The house analogy — the mental model that clicks

RAM
=
A street of numbered houses
x = 42
=
A house with the number 42 inside
p = &x
=
A piece of paper with x's house address written on it
*p
=
Go to that house and look inside → find 42
NULL
=
A blank piece of paper — no address written — you can't go anywhere with it

Memory layout — int x = 42; int *p = &x;

x at 0x1000
42
0x1000
← value of x
p at 0x1008
0x1000
0x1008
42
0x1000
p stores x's address → *p follows it → finds 42
Basic pointer — declare, assign, use
pointer_basic.c
C
#include <stdio.h>

int main() {
    int  x = 42;     // regular int variable
    int *p = &x;     // p is a pointer — stores the address of x

    printf("Value of x:       %d\n",  x);   // 42
    printf("Address of x:     %p\n", &x);   // 0x7fff...  (changes every run)
    printf("Value of p:       %p\n",  p);   // same address as &x
    printf("Value at *p:      %d\n", *p);   // 42  — follows the address

    return 0;
}
terminal
output
Value of x:       42
Address of x:     0x7ffee4b2c8ac
Value of p:       0x7ffee4b2c8ac    ← same as &x
Value at *p:      42                ← follows address, reads value
💡 The address changes every run. Your OS loads the program into a different memory location each time — called ASLR (Address Space Layout Randomization). This is a security feature to prevent attackers from predicting where variables live. So %p output will always look different — that is normal.
2

& and * — What Each Symbol Means

10 – 22 min

& always means one thing: "give me the address of". Simple, consistent, no exceptions.

* means two completely different things depending on context — this is where almost everyone gets confused:

  • In a declaration: int *p; — the * is part of the type — means "p is a pointer-to-int"
  • In an expression: *p = 10; — the * is an action — means "go to the address stored in p and write 10"

The trick to reading pointer code — ask: is this a declaration or an expression?

CodePlain English — say it like this
int x = 42x is an int holding 42
int *pp is a pointer to an int (declaration — * = type marker)
p = &xstore x's address in p
*pgo to the address stored in p and read what's there (expression — * = dereference)
*p = 99go to that address and write 99 there (expression — * = dereference)
printf("%p", p)print the address number itself — not what's there
printf("%d", *p)go to the address, read the int value, print it
& and * — all three outputs
ampersand_star.c
C
#include <stdio.h>

int main() {
    int  x = 42;
    int *p;          // DECLARATION — * means "p is a pointer to int"
    p = &x;          // & means "address of x"

    // Three different things from same pointer:
    printf("x    = %d\n",  x);    // 42  — value directly
    printf("*p   = %d\n", *p);    // 42  — EXPRESSION: follow address, read value
    printf("p    = %p\n",  p);    // 0x... — the address NUMBER itself
    printf("&x   = %p\n", &x);    // same address — & gives address of x

    // x and *p are literally the same memory box!
    *p = 100;   // EXPRESSION: write 100 through the pointer
    printf("After *p=100: x = %d\n", x);  // x is 100 too!

    return 0;
}
terminal
output
x    = 42
*p   = 42
p    = 0x7ffee4b2c8ac
&x   = 0x7ffee4b2c8ac
After *p=100: x = 100
💡 x and *p are the same memory box — not just equal, literally the same location.
Changing *p changes x too — because p is just a second door into x's house. No copying happens. This is the entire power of pointers — you can modify a variable through its address from anywhere in the program.
3

Dereferencing — Following the Address

22 – 32 min

Dereferencing means following a pointer to the value it points to. The * operator in an expression is the dereference operator. It is the opposite of &:

  • & goes from value → address   ("give me your address")
  • * goes from address → value   ("go to that address, show me what's there")
Dereferencing — modify original variable through pointer
dereference.c
C
#include <stdio.h>

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

    printf("Before: x = %d\n", x);    // 42

    *p = 30;   // write 30 into x through pointer
    printf("After *p=30: x = %d\n", x); // 30

    (*p)++;    // increment through pointer (parentheses important!)
    printf("After (*p)++: x = %d\n", x); // 31

    // Multiple pointers to same variable
    int *q = &x;   // q also points to x
    *q = 999;
    printf("After *q=999: x=%d *p=%d\n", x, *p); // both 999

    return 0;
}
terminal
output
Before: x = 42
After *p=30:  x = 30
After (*p)++: x = 31
After *q=999: x=999  *p=999
Passing pointer to function — call by reference
call_by_ref.c
C
#include <stdio.h>

// Function takes POINTER — can modify original variable
void doubleIt(int *num) {
    *num = *num * 2;   // modify the original through pointer
}

// Function takes VALUE — only gets a copy, cannot change original
void tryDouble(int num) {
    num = num * 2;   // changes only the LOCAL copy
}

int main() {
    int x = 10;

    tryDouble(x);
    printf("After tryDouble: x = %d\n", x);   // still 10

    doubleIt(&x);
    printf("After doubleIt:  x = %d\n", x);   // 20!

    return 0;
}
terminal
output
After tryDouble: x = 10    ← value copy — original unchanged
After doubleIt:  x = 20   ← pointer — original changed!
💡 This is why scanf needs &!
scanf("%d", &age) — you pass the address of age so scanf can write the value directly into it from inside the function. Without the &, scanf would only receive a copy and the variable would never change.
pointer arithmetic & arrays
4

Pointer Arithmetic — Moving Through Memory

32 – 45 min

Pointers understand the size of their type. Adding 1 to an int* moves it forward by sizeof(int) = 4 bytes — not just 1 byte. This is what links arrays and pointers so tightly in C.

  • p + 1 → advances 4 bytes (for int*) to the next int slot
  • p + 2 → advances 8 bytes to the one after that
  • arr[i] is literally *(arr + i) — the compiler generates identical code for both

int arr[3] = {10, 20, 30} — memory layout with pointer positions

arr / p
10
+0 bytes
20
+4 bytes
30
+8 bytes
p / p+0
p+1
p+2
Pointer arithmetic — same result as array indexing
ptr_arithmetic.c
C
#include <stdio.h>

int main() {
    int  arr[3] = {10, 20, 30};
    int *p = arr;   // arr is already the address of arr[0]

    // Two ways — identical output
    printf("Array notation:   %d %d %d\n", arr[0],  arr[1],  arr[2]);
    printf("Pointer notation: %d %d %d\n", *(p+0), *(p+1), *(p+2));

    // Walking through array with pointer
    printf("Walking: ");
    for (int i = 0; i < 3; i++) {
        printf("%d ", *p);
        p++;   // advance pointer to next element
    }
    printf("\n");

    // arr[i] IS *(arr+i) — the compiler generates same code
    printf("arr[2] = %d  |  *(arr+2) = %d\n", arr[2], *(arr+2));

    return 0;
}
terminal
output
Array notation:   10 20 30
Pointer notation: 10 20 30
Walking: 10 20 30
arr[2] = 30  |  *(arr+2) = 30
5 common pointer bugs
5

5 Common Pointer Bugs — Never Make These

45 – 58 min

Pointer bugs are the most dangerous in C — they often cause no compiler error but silently corrupt data, crash the program, or create security vulnerabilities. Know these five and you will avoid 90% of pointer mistakes.

⚠️ Bug 1 — Uninitialized pointer (wild pointer)
A pointer that has not been assigned an address contains garbage — it points to a random memory location. Writing through it corrupts whatever is at that random address — or crashes the program.
bug1_wild.c
C
// ❌ WRONG — wild pointer — undefined behaviour
int *p;        // p contains garbage — points anywhere
*p = 10;      // writing to a random address — CRASH or corruption!

// ✅ CORRECT — always initialize
int  x  = 0;
int *p  = &x;  // p points to x — safe to use
// OR if you don't have a variable yet:
int *p2 = NULL; // NULL marks it as "not pointing to anything yet"
⚠️ Bug 2 — Null pointer dereference
NULL is address 0 — which is never valid to read or write. Dereferencing a NULL pointer always crashes the program. Always check before dereferencing.
bug2_null.c
C
// ❌ WRONG — null dereference — always crashes
int *p = NULL;
*p = 10;    // CRASH — address 0 is protected by the OS

// ✅ CORRECT — check before using
int *p = NULL;
if (p != NULL) {
    *p = 10;  // safe — only runs if p actually points somewhere
}
⚠️ Bug 3 — Dangling pointer (use after free)
After calling free(p) the memory is returned to the OS. The pointer p still contains the old address — but that address is no longer yours. Set p = NULL immediately after freeing.
bug3_dangling.c
C
#include <stdlib.h>

// ❌ WRONG — dangling pointer
int *p = malloc(sizeof(int));
free(p);       // memory returned to OS
*p = 99;       // UNDEFINED BEHAVIOUR — p is now "dangling"

// ✅ CORRECT — null the pointer immediately after free
int *p = malloc(sizeof(int));
free(p);
p = NULL;      // any accidental *p use now crashes visibly — better than silent corruption
⚠️ Bug 4 — Off-by-one in pointer arithmetic
You may form a pointer one-past-the-end of an array — but you must never dereference it. It is a valid address to hold, but the memory there does not belong to your array.
bug4_oob.c
C
int arr[3] = {1, 2, 3};
int *p = arr + 3;  // valid: one-past-the-end address is fine to HOLD

// ❌ WRONG — dereferencing one-past-the-end
*p = 5;   // UNDEFINED BEHAVIOUR — that memory is not part of arr

// ✅ Use it only as a sentinel to detect end-of-array:
int *end = arr + 3;
for (int *q = arr; q < end; q++) {
    printf("%d ", *q);  // safe — q never reaches end
}
⚠️ Bug 5 — Confusing * in declaration vs expression
The * symbol means two completely different things. In a declaration it is part of the type. In an expression it is the dereference operator. These look identical but behave entirely differently.
bug5_star_confusion.c
C
int  x  = 42;
int *p  = &x;   // DECLARATION — * means "p is a pointer to int"
                //              NOT "dereference" here

*p = 10;       // EXPRESSION — * means "dereference: follow address, write 10"

// Ask yourself every time:
// Am I DECLARING a new pointer variable? → * = type label
// Am I USING an existing pointer?        → * = dereference operator
⚠️ Pointer bugs give no compiler errors — they fail silently at runtime.
A wild pointer or off-by-one might work fine 99 times and corrupt memory on the 100th run. Always initialize pointers, always check for NULL before dereferencing, always set to NULL after freeing.
6

Quick Reference — All Pointer Syntax

SyntaxWhat it meansOutput / Result
int *p;Declare pointer to int (uninitialized)garbage value — dangerous!
int *p = NULL;Declare pointer, set to NULL (safe empty)p = 0x0
int *p = &x;Declare pointer, point to xp holds x's address
&xAddress of variable x0x7fff... (hex address)
*pDereference — read value at address p holdsSame as x
*p = 99Dereference — write 99 to address p holdsx becomes 99
p + 1Pointer arithmetic — advance by sizeof(type)next element in array
*(p + i)Same as arr[i]element at index i
printf("%p", p)Print the address number itself0x7ffee4b2c8ac
printf("%d", *p)Print the value at the address42 (or whatever is there)
if (p != NULL)Check pointer is valid before usingsafe dereference
quiz
Q

Quick Quiz

Question 1 of 5

Given int x = 42; int *p = &x; — what does printf("%d", *p) output?

Question 2 of 5

What does printf("%p", p) print?

Question 3 of 5

In int *p = &x; — the * before p means:

Question 4 of 5

Given int arr[3] = {10,20,30}; int *p = arr; — what is *(p+1)?

Question 5 of 5

What is the safest way to declare a pointer when you don't have a variable to point to yet?

Lesson Checklist

  • I understand RAM as numbered boxes — each has an address and a value
  • I know a pointer stores an address, not a value
  • I know & always means "address of" — no exceptions
  • I know * in a declaration means "this is a pointer variable"
  • I know * in an expression means "dereference — follow the address"
  • I understand x and *p are the same memory box — same location
  • I know why scanf needs & — it passes the address to write into
  • I understand pointer arithmetic — p+1 moves by sizeof(type) bytes
  • I know arr[i] and *(arr+i) are identical
  • I know all 5 pointer bugs: wild, null deref, dangling, off-by-one, * confusion
  • I always initialize pointers and set to NULL after free
  • I completed the quiz

What to Learn Next

Coming up — advanced pointer topics
  • 🏗️ Pointers and structsptr->member arrow operator Next
  • 📦 Dynamic memory — malloc, calloc, free Next
  • 🔗 Linked lists — nodes pointing to next nodes Advanced
  • 📋 Function pointers — pointers that store function addresses Advanced