← Back to blogs

Pointers in C

May 18, 2021•4 min read

A pointer is just an address. Here's the mental model that made them click, with a live memory diagram you can poke at.

cfundamentalsmemory

Introduction

I am working through Harvard CS50 right now, and I noticed more people kept on dropping throught the course. Almost everyone was gone after we learned about pointers. does a great job at teaching, and when he started drawing the memory out by hand. It helped solidify the concept.

Memory is a big array

Every variable in your program lives somewhere in memory, and every spot in memory has a number: its address. When you write int x = 42;, the compiler picks a spot, say address 0x1000, and puts 42 there. You use the name x, but the machine uses the address.

That's the whole secret. A is just a variable that holds one of those addresses instead of a regular value.

C
int x = 42;      // x lives at some address, holds 42
int *p = &x;     // p holds the address of x

Two operators do all the work:

  • &x means "the address of x". It gives you a pointer.
  • *p means "the thing at the address in p". It follows the pointer, which is called .

Try it

Below are three variables in memory: two ints and a pointer. Click the buttons to run one line of C at a time and watch which box changes. The important thing to notice: *p = 0; changes a different row depending on where p is pointing, and p = &y; changes p without touching x or y at all.

variableaddressvalue
int x0x100042
int y0x10047
int *p0x10080x1000 → x
last line run: int *p = &x;
p holds 0x1000, so *p reads 42

The highlighted row is whatever p currently points at. Writing through *p changes that row, not p itself.

Reading pointer code

The syntax trips people up because * means two different things depending on where it is.

C
int *p;      // declaration: p is a pointer to an int
*p = 5;      // expression: write 5 to wherever p points
int y = *p;  // expression: read wherever p points

I read int *p as "*p is an int", which is actually how the C designers intended it. The star binds to the name, not the type, which is why int *a, b; makes a a pointer and b a plain int.

Why pointers exist

Functions can change your variables

C passes everything by value, so a function gets a copy. If you want a function to modify the caller's variable, you pass its address.

C
void add_one(int *n) {
    *n = *n + 1;
}

int main(void) {
    int count = 10;
    add_one(&count);   // count is now 11
    return 0;
}

Without pointers, add_one would increment its own copy and count would stay 10.

Arrays are pointers in disguise

An array name is basically the address of its first element. arr[2] and *(arr + 2) are the same thing, which is why pointer arithmetic moves by the size of the type: adding 1 to an int * moves 4 bytes, not 1.

C
int arr[4] = {10, 20, 30, 40};
int *p = arr;        // same as &arr[0]
printf("%d\n", *p);        // 10
printf("%d\n", *(p + 2));  // 30

Strings

A C string is a pointer to the first char, and the string ends wherever the first '\0' byte is. Every string function in the standard library is a loop that walks a pointer until it hits that zero.

The bugs you'll write

  • Dereferencing NULL. int *p = NULL; *p = 1; crashes with a segmentation fault. Always check pointers that might be empty.
  • Dangling pointers. Returning the address of a local variable. The variable dies when the function returns, but the pointer still holds its old address and now points at garbage.
  • Uninitialized pointers. int *p; *p = 5; writes to a random address. Sometimes it works, which is worse than crashing.
C
int *bad(void) {
    int local = 7;
    return &local;   // local is gone after this line
}

Conclusion

A pointer is an address. & gets an address, * follows one. Everything else, from pass-by-reference to arrays to strings to linked lists, is built on that. If pointers aren't clicking, stop reading and draw the boxes. It worked for me.