Phase 2 · Arrays, Strings & MemoryModule 9~50 min read

Pointers & Addresses

Master C's central idea: addresses, dereferencing, pointer types, pointer arithmetic, arrays, and safe pointer use.

What you'll learn

A pointer stores the address of an object or function. Pointers let C share storage across function boundaries, traverse arrays efficiently, express optional values, and manage dynamic resources.

By the end, you'll be able to:

  • Create pointers with the address-of operator and access objects by dereferencing
  • Use pointer parameters to modify caller-owned objects
  • Traverse arrays with valid pointer arithmetic
  • Apply null checks and const correctly

Objects & addresses

Every live object occupies storage. The unary & operator obtains its address. A pointer's type describes the kind of object expected at that address.

A pointer stores where another object lives

pointer variable

score_ptr

0x7ff...a4

→

int object

score

92

Key idea

A pointer is not the object itself. It is a value that identifies an object—or deliberately identifies no object with NULL.

Declare & dereference

In a declaration, * introduces a pointer type. In an expression,*pointer accesses the pointed-to object. This second use isdereferencing.

pointer_basics.c
#include <stdio.h>

int main(void) {
    int score = 92;
    int *score_ptr = &score;

    printf("value: %d\n", score);
    printf("through pointer: %d\n", *score_ptr);
    printf("address: %p\n", (void *) score_ptr);

    *score_ptr = 100;
    printf("updated: %d\n", score);
    return 0;
}
The actual address changes between systems and program runs.

Pointer parameters

C still passes a pointer argument by value, but the copied address refers to the caller's object. Dereferencing it lets the function update that shared object.

swap.c
#include <stdio.h>

void swap(int *left, int *right) {
    int temporary = *left;
    *left = *right;
    *right = temporary;
}

int main(void) {
    int first = 10;
    int second = 20;

    swap(&first, &second);
    printf("%d %d\n", first, second);
    return 0;
}

Note

A pointer parameter should document whether it may be null, whether the function reads or writes the object, and how many elements are accessible when it represents a sequence.

Null & invalid pointers

StateMeaningSafe action
NULLDeliberately points to no objectCompare; do not dereference
Uninitialized (wild)Contains an indeterminate addressInitialize before any use
DanglingFormer object no longer existsStop using; reset when useful
ValidPoints to a live compatible objectDereference within its permissions
nullable.c
#include <stdio.h>

void print_value(const int *value) {
    if (value == NULL) {
        printf("No value\n");
        return;
    }
    printf("%d\n", *value);
}

int main(void) {
    int answer = 42;
    print_value(&answer);
    print_value(NULL);
    return 0;
}

Watch out

Dereferencing a null, wild, dangling, misaligned, or otherwise invalid pointer is undefined behavior. Test nullability before dereferencing when null is allowed.

Pointer arithmetic

Adding one to an int * advances by one int, not one byte. Pointer arithmetic is defined only within one array object and its one-past position.

pointer_loop.c
#include <stdio.h>

int main(void) {
    int values[] = {10, 20, 30, 40};
    int *cursor = values;
    int *end = values + 4;

    while (cursor != end) {
        printf("%d ", *cursor);
        cursor++;
    }
    putchar('\n');
    return 0;
}

Watch out

The one-past pointer may be formed and compared, but not dereferenced. Arithmetic between unrelated objects is not a portable way to navigate memory.

Pointers and arrays

In most expressions, an array name converts to a pointer to its first element. Indexing is defined in terms of pointer arithmetic: values[i] means*(values + i).

pointer_array.c
#include <stddef.h>
#include <stdio.h>

int sum(const int *values, size_t count) {
    int total = 0;
    for (size_t i = 0; i < count; i++) {
        total += *(values + i); // same element as values[i]
    }
    return total;
}

int main(void) {
    int values[] = {2, 4, 6, 8};
    printf("%d\n", sum(values, 4));
    return 0;
}

Key idea

Arrays and pointers are closely related, but they are not the same type of object. An array owns all its element storage; a pointer stores an address.

const correctness

pointer_const.c
void examples(void) {
    int value = 10;
    int other = 20;

    const int *read_only = &value;  // may point elsewhere; cannot change *read_only
    int *const fixed = &value;      // cannot point elsewhere; may change *fixed
    const int *const both = &value; // neither operation is allowed

    read_only = &other;
    *fixed = 11;

    (void) read_only;
    (void) both;
}

Read declarations from the identifier outward. const int *p is a pointer to const int; int *const p is a const pointer to int.

Pointers to pointers

A pointer can itself have an address. An int ** is useful when a function must update an int * owned by its caller. Dynamic allocation and linked structures will use this pattern.

pointer_to_pointer.c
#include <stdio.h>

void choose_larger(int **selection, int *left, int *right) {
    *selection = *left >= *right ? left : right;
}

int main(void) {
    int a = 7;
    int b = 12;
    int *chosen = NULL;

    choose_larger(&chosen, &a, &b);
    printf("%d\n", *chosen);
    return 0;
}

Recap & quick check

Key takeaways

  • The address-of operator creates an address value; dereferencing accesses the pointed-to object.
  • Pointer parameters let functions work with caller-owned storage while the pointer itself is passed by value.
  • NULL represents no object and must not be dereferenced.
  • Pointer arithmetic is constrained to a single array and its one-past position.
  • Arrays often convert to pointers in expressions, but arrays and pointers remain distinct concepts.

Quick check

1. What does &score produce?

2. What does *ptr mean in an expression?

3. Where may pointer arithmetic be performed portably?

4. What does const int *value promise?

You can now reason with addresses. Next up: Module 10 — Dynamic Memory Management, where object sizes and lifetimes can be chosen at runtime.