---
title: 'Pointer Arithmetic'
source: 'https://academia.sh/en/courses/c-memory/pointer-arithmetic'
course: 'Memory and Pointers in C'
language: en
updated: '2026-09-26T03:02:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Measures how incrementing and decrementing a pointer advances according to its type's size"
    - 'Computes the difference between two pointers in element units and measures their order with comparison'
    - "Explains why an arithmetic that runs past an array's bounds is never run"
---

# Pointer Arithmetic

Why incrementing a pointer, decrementing it, and subtracting two pointers operates in elements rather than bytes; why an arithmetic that runs past the bounds is never run.

The previous lesson's `local array` row gave a `last - first` difference of **3**: in a
four-element array, there are three steps between the first and last elements. But that
difference is not in bytes — the actual memory distance between `last` and `first` is a
multiple of however many bytes the array's element type takes up. This lesson measures
where that multiplier comes from, and what it means to increment and decrement a pointer.
The class is still **language**: moving between an array's elements requires no allocation
or free call at all.

## Increment and Decrement: Advancing by Type Size

```c
// increment.c — incrementing a pointer advances by its type's size
#include <stdio.h>

int main(void) {
    int array[5] = {10, 20, 30, 40, 50};
    int *p = array;

    printf("%s %d\n", "first element", *p);
    p++;
    printf("%s %d\n", "element after one increment", *p);
    p += 2;
    printf("%s %d\n", "element after three increments", *p);
    p--;
    printf("%s %d\n", "element after one decrement", *p);

    double pair[3] = {1.5, 2.5, 3.5};
    double *d = pair;
    d++;
    printf("%s %.1f\n", "element after one increment on a double array", *d);
    return 0;
}
```

```
first element 10
element after one increment 20
element after three increments 40
element after one decrement 30
element after one increment on a double array 2.5
```

**PO5.** `p++` moves the pointer to the next `int` — the step written is always **one
element**, never one byte. `p += 2` skips two elements at once: from index 1 to index 3.
`p--` steps back by one element. The same `p++` spelling was used on the `pair` array of
`double` values, and it again advanced by exactly **one element** — even though `int` and
`double` have different sizes, the increment operator means "the next element" for both.
The size itself (`sizeof`) was never printed anywhere; the measurement was made only
through the pointed-to **value**.

## The Difference and Order of Two Pointers

```c
// diff_and_order.c — the difference between two pointers is in elements, their order in comparison
#include <stdio.h>

int main(void) {
    int array[6] = {1, 2, 3, 4, 5, 6};
    int *first = &array[0];
    int *middle = &array[3];
    int *last = &array[5];

    printf("%s %d\n", "middle minus first", (int)(middle - first));
    printf("%s %d\n", "last minus middle", (int)(last - middle));
    printf("%s %d\n", "does first come before middle", (int)(first < middle));
    printf("%s %d\n", "does last come before middle", (int)(last < middle));

    int *walker = first;
    int steps = 0;
    while (walker < last) {
        walker++;
        steps++;
    }
    printf("%s %d\n", "steps taken from first to last", steps);
    return 0;
}
```

```
middle minus first 3
last minus middle 2
does first come before middle 1
does last come before middle 0
steps taken from first to last 5
```

**PO6.** `middle - first` gives **3**: there is a gap of three elements between `array[3]`
and `array[0]`. The result of this subtraction is always the **element count**, never the
byte difference between two addresses — the expression `middle - first` prints `3`, and the
addresses themselves are never seen. The comparison `first < middle` gives **1** because
`first` sits before `middle` in the array; this ordering comes from the `<` operator being
defined for two pointers within the same array. The loop built with the condition
`walker < last` advances exactly **5** steps from `first` to `last` — which matches the
`last - first` difference exactly.

## Arithmetic on a `char` Array: When the Element Step Equals the Byte Step

The previous two examples used `int` and `double` arrays; in both, a single increment
advanced by more than one byte, but that advance was never measured directly — only the
pointed-to value was read. The `char` type creates a special case here: `sizeof(char)` is
always **1**, so incrementing a `char *` covers a step of one element and a step of one
byte at the same time. This case comes up often when moving through a string's characters.

```c
// character_walk.c — one increment on a char pointer moves to the next character
#include <stdio.h>

int main(void) {
    char string[] = "abcd";
    char *letter = string;

    printf("%s %c\n", "first character", *letter);
    letter++;
    printf("%s %c\n", "character after one increment", *letter);

    char *last_letter = &string[3];
    printf("%s %d\n", "last letter minus first letter", (int)(last_letter - string));
    printf("%s %c\n", "value of the last character", *last_letter);
    return 0;
}
```

```
first character a
character after one increment b
last letter minus first letter 3
value of the last character d
```

**PO7.** The call `letter++` moves from `string[0]` to `string[1]`; the value read changes
from `'a'` to `'b'`. The difference `last_letter - string` gives **3** — `string[3]` is
three elements from the start of the array. This difference follows the same rule as the
`middle - first` example in the `int` array: the result is in elements. That the difference
also happens to equal a byte distance for `char` is a direct consequence of `sizeof(char)`
being **1** — not an exception to the rule, but the shape the rule itself takes for `char`.

## Row Stepping in a Two-Dimensional Array

The three examples so far used one-dimensional arrays; the increment always advanced by
**one element**. In a two-dimensional array this rule does not change, but the definition
of "element" does: when a row pointer (`int (*)[4]`) is defined over `int matrix[3][4]`,
an increment skips not a single `int` but **an entire row** (4 `int`s).

```c
// row_step.c — a row pointer in a two-dimensional array counts a whole row as one step
#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int (*row)[4] = matrix;

    printf("%s %d\n", "first element of the first row", row[0][0]);
    row++;
    printf("%s %d\n", "first element of the row after one increment", row[0][0]);
    printf("%s %d\n", "third element of the same row", row[0][2]);

    int *flat = &matrix[0][0];
    int *second_row_start = &matrix[1][0];
    printf("%s %d\n", "second row start minus first element", (int)(second_row_start - flat));
    return 0;
}
```

```
first element of the first row 1
first element of the row after one increment 5
third element of the same row 7
second row start minus first element 4
```

**PO8.** The `row` variable has type `int (*)[4]` — it carries the type "address of an
array of four `int`s," not a plain `int *`. When `row++` is called, the pointer moves to
the next **row**: the value of `row[0][0]` jumps from `1` to `5`, that is, to the start of
`matrix`'s second row. This is the same rule as `p++` advancing by one `int` in
`increment.c`, seen on a different type: the step is always the size of the **pointed-to
type**, and here that type is not a single `int` but a row of four `int`s. The difference
between a plain `int *` (`flat`) and the start of the second row comes out to **4** — which
confirms why `int matrix[3][4]` decays to `int (*)[4]` rather than `int **` when passed to
a function: the row size (4) travels as part of the type, otherwise `row++` could not
advance to the correct element.

## The Same Rule on a Dynamically Allocated Array

The three examples so far ran on local (`language` class) arrays. The same arithmetic
rules hold **exactly** on a dynamically allocated block as well — advancing by type size
does not require the pointer to know where the array came from.

```c
// dynamic_increment.c — pointer arithmetic on a dynamically allocated block follows the same rule
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int length = 4;
    int *array = malloc((size_t)length * sizeof *array);
    if (array == NULL) {
        printf("%s\n", "allocation failed");
        return 1;
    }
    for (int i = 0; i < length; i++) {
        array[i] = (i + 1) * 10;
    }

    int *p = array;
    printf("%s %d\n", "first element", *p);
    p += 2;
    printf("%s %d\n", "element after two increments", *p);

    int *last = array + length - 1;
    printf("%s %d\n", "last minus first", (int)(last - array));

    free(array);
    array = NULL;
    return 0;
}
```

```
first element 10
element after two increments 30
last minus first 3
```

**PO9.** `array` is a block allocated with `malloc`, but the call `p += 2` produces
**exactly the same result** as the local array in `increment.c`: it skips two elements,
from `10` to `30`. The difference `last - array` is **3** — the same rule as the
`middle - first` example in `diff_and_order.c`. The difference is not in the arithmetic but
in ownership: this block was requested with `malloc`, so its class here is **caller** —
`main` allocates `array`, `main` uses it, and the obligation to free it stays with `main`
as well. The arithmetic itself is entirely independent of that ownership fact: whether the
block a pointer points to is a local array in the `language` class or a dynamic block in
the `caller` class, how far `p++` advances depends only on the **type**, never on where the
memory came from.

## The Equivalence Between Indexing and Arithmetic

So far, `p[i]` notation and `p + i` notation have each been used separately, but the
relationship between them was never measured. In C, square-bracket indexing is a
**shorthand** defined in terms of arithmetic: the expression `p[i]` means exactly
`*(p + i)`, neither a byte off nor a separate rule.

```c
// index_equivalence.c — p[i] notation and *(p + i) notation point at the same address
#include <stdio.h>

static int index_it(int *p, int i) {
    return p[i];
}

static int add_by_dereference(int *p, int i) {
    return *(p + i);
}

int main(void) {
    int array[5] = {100, 200, 300, 400, 500};
    int *p = array;

    int index_result = index_it(p, 3);
    int arithmetic_result = add_by_dereference(p, 3);
    printf("%s %d\n", "p indexed at three", index_result);
    printf("%s %d\n", "p advanced three times and dereferenced", arithmetic_result);
    printf("%s %d\n", "do the two notations give the same value", (int)(index_result == arithmetic_result));

    int zeroth_index = index_it(p, 0);
    int zeroth_arithmetic = add_by_dereference(p, 0);
    printf("%s %d\n", "zeroth index", zeroth_index);
    printf("%s %d\n", "same as dereferenced p", (int)(zeroth_index == zeroth_arithmetic));
    return 0;
}
```

```
p indexed at three 400
p advanced three times and dereferenced 400
do the two notations give the same value 1
zeroth index 100
same as dereferenced p 1
```

**PO10.** The expressions `p[3]` and `*(p + 3)` give the same value (**400**), and the
equality comparison comes out **1** — confirming that the two notations express exactly
the same operation to the compiler. The equality between `p[0]` and `*p` is an edge case of
the same rule: the zeroth index is identical to dereferencing directly, with no advance at
all. This equivalence explains why all of this lesson's `p++`, `p += n`, and `last - first`
measurements gave results consistent with `array[i]` notation: indexing is a notational
convenience built on top of arithmetic, not a separate mechanism. The class is still
**language**: both `p[3]` and `*(p + 3)` point at the same local array, and the only
difference between them is notation. This equivalence also explains something else: even
`array[i]` notation is itself a form of pointer arithmetic, because `array` decays to the
address of its first element whenever it enters a function or an operator context, and
`array[i]` becomes exactly equivalent to `*(array + i)`.

## The Bounding Measurement

Pointer arithmetic is defined only **within the same array, or one element past its end**
— pointing one past the array's last element (for loop termination) is valid, but
**dereferencing** that address, or advancing the array by two elements past it, is
undefined behavior. In the `diff_and_order.c` example above, `walker` advances during the
loop only as far as the address `last` and stops there — an increment that runs past the
array's bound is never run. This bound is not something the compiler checks: writing
`p += 100` compiles too, but its result is undefined, and this lesson never includes such
a call. The validity of the arithmetic depends on the person writing the code knowing the
array's bounds — the type system does not carry that bound.

The same bound applies to pointer values that fall **outside** a statically allocated
block such as `array`: pointing to one before the start of the array with `p - 1`, or going
further than `last + 1`, falls into the same class of undefined behavior. This is exactly
why the bound is defined as "one element past the end": the standard guarantees just
enough that a loop termination condition (like `walker < last`) can safely reach that one
address, and no more.

This bound also holds between pointers belonging to two **unrelated** arrays: if `int a[4]`
and `int b[4]` are two separately defined local arrays, `&b[0] - &a[0]` compiles but its
result is **undefined** — the subtraction operator only guarantees a meaningful element
count for two pointers within the same array (or one element past it); where two different
arrays sit relative to each other in memory is a detail left to the compiler's decision,
and a program cannot rely on it. Both the `diff_and_order.c` and `dynamic_increment.c`
examples above observe this rule indirectly: both take a difference only between two
pointers **within a single array or a single block**, never comparing two unrelated blocks
like `a` and `b`.

## Summary

- Pointer increment and decrement (`p++`, `p--`, `p += n`) always advance by **the type's
  size**; the step count is in elements, never in bytes.
- The difference between two pointers (`last - first`) gives the **element count** between
  them; the address itself is never printed.
- Two pointers can be **ordered** with comparison operators like `<` and `>` — within the
  same array, that ordering matches their position order in the array.
- **Pointing** one past an array's bound is valid, but **dereferencing** that address is
  undefined behavior; taking the difference between pointers of two unrelated arrays falls
  into the same class — neither was run in this lesson.
- `p[i]` notation is a shorthand for `*(p + i)`; the two notations always point at the same
  address and return the same value.
- The arithmetic rules do not change whether the block a pointer points to is a local
  array (`language`) or a dynamically allocated block (`caller`); the step depends only on
  the type, never on the memory's source. This is why the memory this lesson measures is
  in the **language** class: the rule is set by the language itself, not by the memory's
  owner.

## Next Step

An array's name decays on its own into the address of its first element in most contexts —
the `int *first = array` notation already used this without dwelling on it. The next
lesson, Arrays and Pointers, measures when this decay happens, how an array is passed to a
function (and why without length information), and the first step into the **caller**
class: the ownership of a dynamically allocated array.
