TRT
Sign in

Foundations · Module 1 · Lesson 6 of 7

Your First Pointer: Addresses and Dereferencing

Learn what a pointer is, how to get a variable's address with &, how to declare and dereference a pointer with *, and why pointers matter in C.

FreeAbout 15 minutes

By the end you can

  • Explain that a pointer stores a memory address, not a value
  • Use the & operator to get a variable's address
  • Declare a pointer and dereference it with *
  • Trace pointer assignment and dereference step by step

Builds on Text in C: Working with Strings.

01 · A question first

You have a variable age = 25. You want another variable that does not store a number, but instead stores *where* age lives in memory. How do you find a variable's address, store it, and read the value at that address?

Make a guess, then step through the trace below to check it.

02 · Watch it run

Tracing pointer declaration, address-of, and dereferencestep 1 of 3
int age = 25;int *ptr = &age;printf("%d\n", *ptr);
  1. slot 0age: 25(looked at in this step)
  2. slot 1ptr: uninitialized
  3. slot 2*ptr: unread

Variable age is created and holds 25 at some memory address.

Machine work: allocate age; store 25

03 · The idea

Start with the problem

Think back to Lesson 3, where we saw that C passes function arguments by value. Suppose you want to write a function swap(a, b) that exchanges the values of two integer variables x = 10 and y = 20 in main, or a function that updates a player's health and score at the same time. If you write:

c
void bad_swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

Calling bad_swap(x, y) copies 10 into a and 20 into b, swaps the two local copies inside bad_swap, and then destroys a and b when the function returns—leaving x and y in main stuck at 10 and 20.

And remember scanf("%d", &age) from Module 0: how was scanf able to reach into main and fill age when bad_swap could not? Because we handed scanf the memory address where age lives (&age). A variable that stores a memory address is called a pointer, and learning how to hold addresses and follow them is the key that unlocks all of C.

How the idea works

Computer memory (RAM) is a giant numbered row of byte cells. Every byte has a unique numeric address (often written in hexadecimal, like 0x7ffd1040). When you declare int age = 25;, the compiler picks a 4-byte slot in memory—say at address 0x7ffd1040—and writes the number 25 inside those 4 bytes.

A pointer is simply a variable whose value is the address of another variable:

c
int age = 25;
int *ptr = &age;
printf("%d", *ptr);

Step through the trace above to see each line in action:

  1. Allocating the target variable (Step 1): Line 1 creates age and stores 25 in it.
  2. Taking the address with & (Step 2): On line 2, &age evaluates to the memory address where age lives. The declaration int *ptr creates a pointer variable named ptr (typed as "pointer to int") and stores age's address inside ptr.
  3. Dereferencing with * (Step 3): On line 3, the expression *ptr dereferences the pointer: it tells the CPU, "Look at the address stored inside ptr, travel to that memory location (age), and read the int stored there (25)."

The two jobs of the * symbol

Beginners often get tripped up because C uses the asterisk * for two different jobs depending on where it appears:

  1. In a declaration (int *ptr;): The * is part of the type. It announces that ptr is not an int, but a pointer to an int (an address where an int lives).
  2. In an expression (*ptr = 99; or printf("%d", *ptr);): Once ptr already exists, putting * in front of ptr is the dereference operator. It means "the int variable located at the address stored in ptr."

Because *ptr refers directly to the age slot in memory, reading *ptr reads age, and assigning to *ptr modifies age in place:

c
int age = 25;
int *ptr = &age;
 
*ptr = 99;               /* writes 99 directly into age's memory slot */
printf("%d", age);     /* prints 99! */

Solving the swap problem with pointers

Now we can write a real swap function that modifies main's variables. Instead of passing copies of x and y, main passes their addresses &x and &y, and swap accepts two int * pointers:

c
#include <stdio.h>
 
void swap(int *pa, int *pb) {
    int temp = *pa;  /* read the int at address pa */
    *pa = *pb;       /* copy the int at pb into the slot at pa */
    *pb = temp;      /* write temp into the slot at pb */
}
 
int main(void) {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x = %d, y = %d", x, y);  /* prints: x = 20, y = 10 */
    return 0;
}

The NULL pointer: pointing nowhere

What if a pointer does not have a valid variable to point at yet? Never leave a pointer uninitialized (int *p;), because its value is indeterminate. Instead, initialize it to the null pointer constant NULL (defined by standard headers such as <stddef.h>), which compares unequal to any valid pointer to an object or function. Do not assume that a null pointer is represented by the integer bit pattern 0x0:

c
int *ptr = NULL;
if (ptr != NULL) {
    printf("%d", *ptr);  /* safe: only dereferences when non-NULL */
}

Why it is correct

Notice why a pointer always has a target type (int *, double *, char *) even though every memory address on a 64-bit machine is 8 bytes wide. The address &age only tells the CPU the first byte where age begins in RAM. When you dereference *ptr, the CPU needs to know how many bytes to read starting at that address (1 byte for char *, 4 bytes for int *, 8 bytes for double *) and how to interpret the bits (signed integer vs IEEE-754 floating-point). Strong pointer types ensure *ptr always reads and writes the exact width of the underlying variable.

What it costs

  • Space: Pointer sizes are chosen by the target implementation. Several object-pointer types often have the same size on a 64-bit system, but portable code should use sizeof rather than assume 8 bytes.
  • Time: Taking an address is usually inexpensive, but dereferencing can involve cache misses, address translation, or faults. Passing a pointer can avoid copying a large object, but it does not make access free.

Pitfalls and variations

MistakeWhat goes wrongFix
int *p; *p = 10;Uninitialized ("wild") pointer writes 10 to a random address and crashesPoint p at a valid int variable (int x; int *p = &x;) before dereferencing
int *p = NULL; printf("%d", *p);Dereferencing NULL triggers an immediate segmentation faultAlways guard nullable pointers with if (p != NULL)
Declaring int* p1, p2;Only p1 is a pointer (int *); p2 is a plain int!Put the * on each pointer variable: int *p1, *p2;

Where it is used

Pointers are the backbone of systems programming in C: they enable functions to write back multiple outputs (scanf, swap), pass large buffers without copying, allocate dynamic memory at runtime (malloc), and link nodes together into trees, graphs, and operating system kernel queues.

You now have all the core building blocks of C programming. In the next module, you will look directly under the hood—tracing how the compiler turns your source code into an ELF binary, how integers and fixed-width types are encoded in binary, how stack frames are laid out in memory, and how pointer arithmetic powers high-performance systems code.

04 · Prove it

Question 1 of 3 · answer from memory

What does &age give you?

05 · Practice

Something wrong with this lesson? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save