Part of C in 100s

C in 100 Seconds: Pointers | Episode 17

Celest KimCelest Kim

Video: C in 100 Seconds: Pointers — Two Ways to Access the Same Memory | Episode 17 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C Pointers: Two Ways to Access the Same Memory

A pointer holds a memory address. int *ptr = &x; makes ptr point at x. *ptr reads or writes through the pointer. Two names, one memory location.

Pointers are C's most powerful and most dangerous feature. They're how the language exposes memory directly. Once you internalize them, the rest of C clicks.

The basic shape

#include <stdio.h>

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

  printf("Value of x: %d\n", x);          // 42
  printf("Address of x: %p\n", &x);       // 0x7ffe...
  printf("ptr holds: %p\n", ptr);         // same as &x
  printf("Value at ptr: %d\n", *ptr);     // 42

  *ptr = 99;
  printf("x is now: %d\n", x);            // 99

  return 0;
}

Three operators

int x = 42;
int *ptr;        // declare: ptr is a pointer to int
ptr = &x;        // assign: ptr now holds the address of x
*ptr = 99;       // dereference: write 99 to the location ptr points at
  • * in declarationint *ptr declares ptr as "pointer to int." (Some style guides write int* ptr; both compile.)
  • &xaddress-of operator. Returns the memory address of x.
  • *ptrdereference operator. Returns or writes the value at the address.

The same * symbol means different things in different places:

  • int *ptr — declaration: pointer type.
  • *ptr — expression: dereference the pointer.

Memory model

Variable    Address    Value
x           0x1000     42
ptr         0x2000     0x1000

x lives at some address (let's say 0x1000) and holds the value 42. ptr lives at some other address (0x2000) and holds the value 0x1000 — the address of x.

Reading *ptr says "go to the address in ptr (0x1000), read what's there (42)."

Writing *ptr = 99 says "go to that address, write 99 there." x and *ptr are now both 99.

Why pointers?

Three main uses:

  1. Modify the caller's variable. Pass &x to a function; the function writes through the pointer (episode 19).
  2. Avoid copying large data. Pass a pointer to a struct, not the whole struct.
  3. Dynamic memory. malloc returns a pointer to heap memory (episode 20).

NULL

int *ptr = NULL;   // ptr points to "no address"
if (ptr != NULL) {
  *ptr = 5;
}

NULL is the special "points to nothing" value (defined in <stddef.h> and most headers). Always check before dereferencing — *NULL segfaults.

In modern C, nullptr (C23) is the typed null. Pre-C23, NULL is a macro that expands to ((void*)0) or 0 depending on the implementation.

Pointer types matter

int x = 42;
int *p = &x;      // OK: types match
double *q = &x;   // ERROR: type mismatch
double *q2 = (double*)&x;   // compiles, but wrong (treats int bytes as double)

A pointer type tells the compiler how to interpret the bytes at the address. int * means "4 bytes here, treat as int." double * means "8 bytes here, treat as double."

Casting between pointer types compiles but reinterprets bytes — a footgun.

void*

void *vp = &x;      // any pointer type
int *p = (int*)vp;  // cast back to specific type

void * is "pointer of unknown type." Used for generic APIs (malloc returns void*; qsort takes void *). Can't be dereferenced directly — cast to a specific type first.

Pointer to pointer

int x = 42;
int *p = &x;
int **pp = &p;     // pointer to pointer
**pp = 99;         // double-dereference: x is now 99

int ** is "pointer to pointer to int." Episode 29 covers this.

Common mistakes

Uninitialized pointer. int *p; *p = 5;p holds garbage; dereferencing crashes (or worse, silently corrupts memory). Always initialize: int *p = NULL; or int *p = &x;.

Dereferencing NULL. int *p = NULL; *p = 5; segfaults.

Returning pointer to local. int* foo() { int x = 5; return &x; }x is destroyed when foo returns. The pointer dangles. Catastrophic.

Forgetting the &. scanf("%d", x) — passes x's value as the address. Crash.

Forgetting the * to dereference. int *p = &x; printf("%d", p); prints the address, not the value.

Pointer arithmetic with wrong type. int *p = arr; p + 1 advances by 4 bytes (one int). Don't manually compute byte offsets — let the type system handle it.

Pointers vs references (briefly)

C++ has int &ref = x; (references) which look like normal variables but are aliases. C doesn't — pointers are the only way.

Pros of pointers: explicit; you know when something is "by reference." Cons: more error-prone (NULL, dangling).

Why this matters

Pointers underpin:

  • Function arguments that modify caller state (scanf, swap).
  • Dynamic data structures (linked lists, trees).
  • Heap memory (malloc).
  • Generic data structures (qsort, hash tables).
  • Strings (which are char *).

If you don't get pointers, you can't read most non-trivial C code. If you do, the language's design suddenly makes sense.

What's next

Episode 18: pointer arithmetic. Walking an array with a pointer. *(ptr + i) is the same as ptr[i].

Recap

int *p = &x declares a pointer; &x is the address-of operator. *p dereferences (read/write through the pointer). NULL means "points to nothing"; never dereference NULL. Pointer types tell the compiler how to interpret bytes — mismatched types cast successfully but break semantically. void * for generic pointers; cast before use. Pointers underpin function arguments-by-reference, dynamic memory, and dynamic data structures.

Next episode: pointer arithmetic.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.