Pointer Arithmetic in C: Rules, Worked Examples, and Common Errors

Understand how C pointers move across arrays, where arithmetic is valid, and why one-past pointers are useful but never readable. Includes code, traces, and common traps.

KnowledgeGate Team

Exam prep & CS education

Updated 10 Sep 20266 min read

p + 1 does not mean "add one byte". Pointer arithmetic advances in elements, pointer subtraction returns an element count, and a loop may reach one position beyond an array but must never read there. Correct tracing tracks three things after every operation: the pointer's array index, whether that position is dereferenceable, and the resulting value or output. These rules also underpin array traversal in the Coding & DSA Courses for Placements learning path.

1. The mental model: pointer arithmetic moves in elements

Start with:

int values[5] = {11, 22, 33, 44, 55};
int *p = &values[1];

Here, p points to element 1, so *p is 22. The expression p + 2 points to element 3, so *(p + 2) is 44. It advances across two complete int objects, whatever sizeof(int) is on that implementation.

The useful operations are p + n, p - n, p++, p--, p += n, and p -= n. Subtracting two pointers into the same array gives the number of elements between them. C does not define adding two pointers, multiplying a pointer, or dividing a pointer.

For a valid index i, values[i] is equivalent to *(values + i). That is an expression equivalence. It does not make an array object and a pointer object identical types.

2. Complete worked example: move, dereference, and subtract

This C17 program keeps each operation visible:

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

int main(void) {
    int values[5] = {11, 22, 33, 44, 55};
    int *p = &values[1];
    int *q = &values[4];

    printf("*p = %d\n", *p);
    printf("*(p + 2) = %d\n", *(p + 2));
    printf("*(q - 1) = %d\n", *(q - 1));
    printf("q - p = %td\n", q - p);

    ++p;
    printf("after ++p, *p = %d\n", *p);
    return 0;
}

Trace it before looking at the output:

  1. p begins at index 1, whose value is 22.

  2. p + 2 moves from index 1 to index 3, whose value is 44.

  3. q is at index 4, so q - 1 is index 3, again giving 44.

  4. q - p is 4 - 1 = 3 elements. It is not a byte count.

  5. ++p moves p from index 1 to index 2, whose value is 33.

Pointer subtraction has type ptrdiff_t, declared through <stddef.h>. The %td conversion prints that type. The exact output is:

*p = 22
*(p + 2) = 44
*(q - 1) = 44
q - p = 3
after ++p, *p = 33

Save the source as pointer_arithmetic.c, then compile it with:

cc -std=c17 -Wall -Wextra -pedantic pointer_arithmetic.c -o pointer_arithmetic
Diagram of an int array with pointers p and q, showing p + 2 and q - 1 reaching the same element and q - p equal to 3.

3. Traverse an array safely with a one-past end pointer

A pointer loop can use the position after the last element as its stopping sentinel:

int numbers[4] = {3, 6, 9, 12};
int total = 0;

for (int *it = numbers; it != numbers + 4; ++it) {
    total += *it;
}
printf("total = %d\n", total);

The running total is 0 -> 3 -> 9 -> 18 -> 30, so the program prints total = 30.

numbers + 4 is a permitted one-past pointer. The iterator may be compared with it, but *(numbers + 4) is invalid because that pointer does not designate an element. The loop tests it != numbers + 4 before dereferencing, so the body never reads the sentinel. C Programming & Data Structures gives the broader roadmap for using this array-pointer relationship in data structures.

Diagram of the numbers array with a one-past sentinel, tracing the running total to 30 as the loop stops before the dashed box.

4. Pointer subtraction and comparisons have an array boundary

With the earlier values array, these calculations are valid:

  • &values[4] - &values[1] is 4 - 1 = 3.

  • &values[1] - &values[4] is 1 - 4 = -3.

  • &values[2] < &values[4] is true.

The difference is measured in elements, not bytes. As a safe beginner rule, subtract or order pointers only when they refer to elements of the same array object, including its one-past position. Do not subtract pointers into two independent arrays, even if printed machine addresses appear close.

Comparison and dereference are separate questions. values + 5 may serve as the end pointer for this five-element array, and values + 5 - values is 5. However, values[5] and *(values + 5) are both out of bounds.

5. The pointed-to type controls the stride

Consider three arrays without assuming any concrete platform sizes:

char letters[3] = {'A', 'B', 'C'};
int counts[3] = {10, 20, 30};
double rates[3] = {1.5, 2.5, 3.5};

letters + 1 reaches 'B', counts + 1 reaches 20, and rates + 1 reaches 2.5. In each case, the pointer moves by one complete element of its own pointed-to type.

The type model uses the size represented by sizeof *ptr, but programmers write ptr + 1, not ptr + sizeof *ptr. C applies the scaling automatically. An unsigned char * may inspect an object's representation byte by byte, but that is a separate operation from traversing an int array. Do not cast an int * merely to force byte-sized movement.

6. Common errors: bounds, unrelated pointers, and *p++

Four forms deserve an immediate check:

  • p + q has no defined pointer-addition meaning.

  • Arithmetic on a null pointer is invalid.

  • a_end - b_start is not a valid distance when a and b are different arrays.

  • Forming or dereferencing a pointer outside an array plus its one-past position is invalid.

The repair is to preserve the array base and a verified element count, then keep every computed pointer within that range.

Precedence creates a different trap:

int a[2] = {7, 9};
int *p = a;
int x = *p++;

*p++ parses as *(p++). Therefore, x becomes 7 and p moves to &a[1]. Reset p = a, then evaluate int y = (*p)++;. Now y becomes 7, a[0] becomes 8, and p remains at &a[0].

By contrast, *++p moves the pointer first and reads the new element, while ++*p increments the pointed-to integer. Use parentheses when the intent is not immediately obvious, and compile with warnings. A precedence mistake can still be valid C.

7. How exams and interviews test pointer arithmetic

Stable question forms ask you to predict output, identify the element reached, calculate a same-array difference, spot a one-past dereference, or distinguish *p++ from (*p)++.

Dry-run this example:

int a[4] = {2, 4, 8, 16};
int *p = a + 1;
printf("%d %td\n", *(p + 2), (a + 4) - p);

It prints 16 3. The pointer p begins at index 1, so p + 2 reaches index 3 and reads 16. The one-past pointer a + 4 is three elements after index 1, so (a + 4) - p is 4 - 1 = 3.

Draw index positions before thinking about possible byte addresses. C Programming for Teaching CS Exams extends the exam-oriented view, while Coding for Placements supports broader coding-test practice.

8. Practice checks, the short version, and next step

Answer these before running them:

  1. For int v[] = {5, 10, 15, 20}, *(v + 2) is 15.

  2. If int *p = &v[3], then p - v is 3.

  3. v + 4 may be an end pointer, but it must not be dereferenced.

  4. With p = v, *p++ yields 5, then moves p to &v[1].

Keep four rules: pointer movement is in elements; arithmetic stays within one array plus its one-past position; subtraction yields an element count; and one-past is a sentinel, not an element. If you want to study pointers inside a complete C sequence, continue with the C Language Course: Concepts, MCQs & Coding.