Pointers: Understanding Memory Addresses

This is the lesson where C “clicks.”

Pointers seem scary until you realize they’re just addresses. Numbers that tell you where something lives in memory.

This is also the lesson most people skip. They retreat to languages that hide memory from them. They spend their careers wondering why things are slow or broken.

The people who push through this lesson become the ones who debug the problems everyone else gives up on.

Let’s make you one of them.

The House Address Idea

Imagine your friend asks where you live. You don’t carry your house to them. You give them your address. “123 Main Street.”

That address is just a number (or name) that tells them how to find your house.

Computer memory works the same way. Every piece of data in your program lives at an address - a number that says where to find it.

A pointer is a variable that stores an address. Instead of storing a number like 42, it stores an address like “location 2000 in memory.”

Memory: Picture It

Think of your computer’s memory as a very long row of numbered mailboxes. Each mailbox:

  • Has a number (its address)
  • Can hold a small piece of data

When you create a variable like int x = 42;, the computer:

  1. Finds an empty mailbox
  2. Remembers that mailbox is called x
  3. Puts 42 in it
Address    What's Inside
-------    -------------
1000       42           <- This is x
1001       ...
1002       ...
1003       ...

Your First Pointer

int x = 42;       // A regular variable - stores the value 42
int *p = &x;      // A pointer - stores the ADDRESS of x

Let’s break down the new symbols:

  • int *p - This declares p as a “pointer to an integer.” The * in the declaration means “this variable will hold an address.”
  • &x - The & means “the address of.” So &x means “the address where x lives.”

If x lives at address 2000, then p contains the number 2000.

Variable    Address    What's Inside
--------    -------    -------------
x           2000       42
p           2008       2000    <- p holds x's address!

The Two Operators: & and *

These two symbols are the key to understanding pointers. Here’s an easy way to remember them:

  • & means “address of” (& looks like an ‘a’ if you squint)
  • * is a star - and pointers are stars you follow to find data

The & Operator: “Address of”

The & gives you the address of a variable:

int x = 42;
printf("x lives at address %p\n", &x);

%p is the format specifier for printing addresses. You’ll see something like 0x7ffd5e8a3c4c - that’s the address written in hexadecimal (base 16) instead of regular numbers.

The * Operator: “Follow the Star”

When used on a pointer, * goes to the address and gets what’s there:

int x = 42;
int *p = &x;

printf("x = %d\n", x);    // 42 (the value directly)
printf("*p = %d\n", *p);  // 42 (follow the address in p, get what's there)

Think of *p as “follow the star to find the data.”

Why This Matters

Remember how functions get copies of their arguments? That meant we couldn’t write a function to change a variable.

Pointers fix that.

Without Pointers (Doesn’t Work)

void try_to_change(int n) {
    n = 100;  // Only changes the local copy!
}

int main(void) {
    int x = 5;
    try_to_change(x);
    printf("x = %d\n", x);  // Still 5!
    return 0;
}

With Pointers (Works!)

void actually_change(int *p) {
    *p = 100;  // Follow the address, change what's there
}

int main(void) {
    int x = 5;
    actually_change(&x);  // Pass the ADDRESS of x
    printf("x = %d\n", x);  // Now it's 100!
    return 0;
}

When we pass &x, we’re saying “here’s where x lives.” The function can then go to that address and change what’s there.

This is why scanf needs the &:

int age;
scanf("%d", &age);  // scanf needs to know WHERE to put the number

scanf is asking “where should I store the number the user types?” You answer by giving it the address.

Step By Step

Let’s trace through this code:

int x = 42;
int *p = &x;
*p = 100;

Step 1: int x = 42;

  • Computer finds empty space at address 2000
  • Puts 42 there
  • Remembers “x” means “address 2000”

Step 2: int *p = &x;

  • Computer finds empty space at address 2008
  • &x means “the address of x” = 2000
  • Puts 2000 there
  • Remembers “p” means “address 2008”

Step 3: *p = 100;

  • Look in p (at address 2008) → contains 2000
  • Follow that address (go to 2000)
  • Put 100 there

Now if you check x, it’s 100. We never mentioned x by name in step 3!

Pointers and Arrays

Here’s something cool: arrays and pointers are closely related.

When you create an array, the array name is basically a pointer to the first element:

int numbers[] = {10, 20, 30, 40, 50};

// These do the same thing:
printf("%d\n", numbers[0]);   // 10 (using array notation)
printf("%d\n", *numbers);     // 10 (using pointer notation)

You can do math with pointers. Adding 1 to a pointer moves to the next element (not the next byte):

int numbers[] = {10, 20, 30, 40, 50};
int *p = numbers;  // p points to the first element

printf("%d\n", *p);        // 10 (first element)
printf("%d\n", *(p + 1));  // 20 (second element)
printf("%d\n", *(p + 2));  // 30 (third element)

In fact, numbers[2] is exactly the same as *(numbers + 2). The square bracket notation is just a shortcut!

Walking Through an Array

You can use a pointer to visit every element:

int numbers[] = {10, 20, 30, 40, 50};
int length = sizeof(numbers) / sizeof(numbers[0]);

// Using array indexing (you know this)
for (int i = 0; i < length; i++) {
    printf("%d ", numbers[i]);
}
printf("\n");

// Using a pointer
for (int *p = numbers; p < numbers + length; p++) {
    printf("%d ", *p);
}
printf("\n");

Both print: 10 20 30 40 50

Pointer Subtraction

You can subtract pointers to find how far apart they are:

int arr[] = {10, 20, 30, 40, 50};
int *start = &arr[0];
int *end = &arr[4];

printf("Distance: %ld elements\n", end - start);  // 4

This tells you how many elements are between two pointers.

NULL: The “Nowhere” Address

Sometimes a pointer doesn’t point to anything yet. We use NULL to mean “no address”:

int *p = NULL;  // p doesn't point anywhere

if (p != NULL) {
    printf("p points to: %d\n", *p);
} else {
    printf("p doesn't point anywhere\n");
}

Important: If you try to use *p when p is NULL, your program will crash. Always check if a pointer is NULL before using it (if you’re not sure it’s valid).

A Visual Summary

int x = 42;      →  A mailbox labeled "x" containing 42
int *p = &x;     →  A mailbox labeled "p" containing x's address

&x               →  "What's x's address?" (gives you the address)
*p               →  "Follow the star, what's there?" (gives you 42)

*p = 100;        →  "Follow the star, put 100 there"
                    Now x is 100!

Try It Yourself

  1. Write a function void swap(int *a, int *b) that swaps two integers. If a is 5 and b is 10, after calling swap they should be 10 and 5.
  2. Write a function that takes a string and counts how many times the letter ’e’ appears (use a pointer to walk through the string)
  3. What happens if you try to use *p when p is NULL? Try it!

Common Mistakes

  • Forgetting to initialize pointers: A pointer that hasn’t been set contains garbage (a random address)
  • Confusing * in declarations vs expressions: In int *p, the * says “p is a pointer.” In *p = 5, the * says “follow the address.”
  • Forgetting the & when passing addresses: change(x) passes the value. change(&x) passes the address.

Next Up

In Part 11, we’ll learn about dynamic memory - how to ask for memory while your program is running, and how memory is organized. This is where you learn what’s really happening inside the computer.


Enjoyed This?

If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.