LearnCodeVisually

Pointers in C Visualized: Addresses and Dereferencing

Watch a pointer store the address of an integer, read the integer using *p and update it through the pointer. Addresses shown are illustrative.

Explore the full interactive C pointer lesson →

Start with working C code

The pointer p stores the address of x. The expression *p accesses the object at that address; assigning to *p changes x.

#include <stdio.h>
int main(void) {
    int x = 12;
    int *p = &x;
    printf("%d\n", *p); /* 12 */
    *p = 24;
    printf("%d\n", x);  /* 24 */
    return 0;
}

Interactive pointer visualization

Use the controls to follow the C example above. The labels describe the current model state.

The explanatory material on this page is also available without JavaScript. Enable JavaScript to use the visual step controls.

Ready to explore the example.

What does a pointer hold?

A pointer is a value that identifies an object or function in memory. In int *p = &x;, &x obtains the address of x, and p stores that address. The type int * indicates that dereferencing p gives access to an int.

Our diagram uses the made-up address 0x1000 purely to illustrate the relationship. Real addresses are selected by the runtime environment and will vary.

The difference between & and *

&x is the address-of expression. *p is a dereference expression that accesses the pointed-to object when p is valid. In a declaration, int *p instead indicates the pointer type. These uses of the asterisk are related but appear in different grammatical contexts.

A pointer must refer to a valid, suitably typed object before you dereference it. Dereferencing a null, uninitialized or dangling pointer is invalid and can lead to undefined behavior. Always understand pointer lifetime and initialization.

Practice predicting pointer behavior

  1. After int *p = &x;, what is stored in p and what is stored in x?
  2. What does *p evaluate to before the assignment?
  3. Why does assigning *p = 24; change x?
Answers

The pointer stores the address of x; x stores the integer 12. Dereferencing p reads 12. Since p refers to x, writing through *p changes the same object to 24.

Open the full C pointers lesson →

Why pointers matter: update a variable from a function

C passes arguments by value. Giving a function an integer copies the integer; changing the parameter does not change the caller's variable. Giving it a pointer copies the pointer value instead. That copied pointer can still refer to the caller's original object.

#include <stdio.h>

void add_one_copy(int value) {
    value += 1;
}

void add_one_at(int *value) {
    if (value != NULL) {
        *value += 1;
    }
}

int main(void) {
    int score = 9;
    add_one_copy(score);
    printf("%d\n", score);  /* still 9 */
    add_one_at(&score);
    printf("%d\n", score);  /* now 10 */
    return 0;
}

Save this as pointer_update.c. With GCC installed, compile using gcc -std=c17 -Wall -Wextra -pedantic pointer_update.c -o pointer_update and run the resulting program. The two output lines are 9 and 10.

  1. add_one_copy(score) starts with a separate integer parameter containing 9. Its change to 10 disappears when the call ends.
  2. &score supplies the address of the caller's integer. The pointer parameter receives a copy of that address.
  3. *value += 1 accesses the caller's integer through the pointer. The caller therefore observes 10 after the function returns.

The null check handles NULL, but does not prove that any non-null pointer is safe. The caller must supply the address of a live, writable integer. Here, score stays alive throughout both calls. Also keep arithmetic within the range of int; overflowing a signed integer is undefined behavior.

Predict, then test

  1. What happens after two calls to add_one_at(&score) starting from 9?
  2. Does assigning value = NULL; inside the function change score?
  3. Why is add_one_at(score) incorrect?
Answers and reasoning

Two calls produce 11. Assigning to value changes only the local pointer parameter, not the caller's integer. The incorrect call supplies an integer where the function expects int *; the compiler must diagnose this type mismatch. Pass &score to supply the address. Do not ignore compiler diagnostics.

Use this pattern when a function needs to modify an existing object. For a single computed result, returning an integer is often simpler: pointers are useful when their shared-object behavior is actually needed. Compare the function return-value walkthrough.