C Pointer Arithmetic
Pointer arithmetic is the part of C that lets a pointer move from one array element to another. It matters because arrays, strings, buffers, and many low-level algorithms are built around the idea of walking through contiguous memory one element at a time.
The important surprise is that p + 1 does not usually mean one byte forward. It means one object of the pointer’s pointed-to type forward, which is why pointer arithmetic works naturally with arrays but becomes dangerous outside valid bounds.
Overview: How Pointer Arithmetic Works
A pointer stores an address, but C does not treat pointer arithmetic as plain integer arithmetic. If p has type int *, then p + 1 points to the next int position. If p has type double *, then p + 1 points to the next double position. The compiler scales the movement by sizeof *p.
This scaling is what makes arrays and pointers closely related. An array stores its elements contiguously: element 0, then element 1, then element 2, and so on. In most expressions, an array name is converted to a pointer to its first element. Therefore, if int values[4] exists, values often behaves like &values[0], and values + 2 points at values[2].
The expression a[i] is defined in terms of pointer arithmetic: it means *(a + i). This equivalence is useful for understanding how indexing works internally, but it does not mean pointer arithmetic is always clearer than indexing. In many programs, indexes are easier to read. Pointer arithmetic shines when you are processing a range represented by a beginning pointer and an end pointer.
C allows pointer addition and subtraction only in a narrow, important context: within the same array object, including a special one-past-the-end pointer. The one-past pointer points just after the last element. It is legal to form and compare, but not legal to dereference. For an array of count elements, array + count is the usual end marker for loops.
You may subtract two pointers only when both point into the same array object, or one points one past that same array. The result has type ptrdiff_t, from <stddef.h>, and gives the distance in elements, not bytes. Comparing unrelated pointers, subtracting unrelated pointers, or walking outside an array produces undefined behavior.
Syntax
pointer + integer
pointer - integer
pointer++
pointer--
pointer2 - pointer1
*pointer
*(pointer + index)
| Expression | Meaning |
|---|---|
p + n |
A pointer n elements after p, scaled by the pointed-to type. |
p - n |
A pointer n elements before p. |
p++ |
Move p to the next element position after using its old value. |
++p |
Move p first, then use the new pointer value. |
last - first |
The number of elements between two pointers in the same array, as ptrdiff_t. |
*(p + i) |
Dereference the element reached by moving i positions from p. |
array + count |
The one-past-the-end pointer, valid for comparison but not dereference. |
Pointer arithmetic works on object pointers such as int *, double *, and char *. It does not work on void * in standard C because void has no size. When you need byte-by-byte movement through raw storage, use a character pointer such as unsigned char *.
Examples
Move Through an Array
#include <stdio.h>
int main(void)
{
int values[] = {10, 20, 30, 40};
int *p = values;
printf("First: %d
", *p);
p++;
printf("Second: %d
", *p);
printf("Fourth: %d
", *(values + 3));
return 0;
}
Output:
First: 10
Second: 20
Fourth: 40
The name values converts to a pointer to the first element. After p++, p points to the second int, not merely to the next byte. The expression *(values + 3) reaches the same object as values[3].
Sum a Range With Begin and End Pointers
#include <stdio.h>
#include <stddef.h>
int main(void)
{
int readings[] = {6, 9, 3, 8, 4};
size_t count = sizeof readings / sizeof readings[0];
int *current = readings;
int *end = readings + count;
int total = 0;
while (current < end) {
total += *current;
current++;
}
printf("Total: %d
", total);
printf("Count: %zu
", count);
return 0;
}
Output:
Total: 30
Count: 5
This loop uses the common range pattern: current starts at the first element, and end is the one-past-the-end pointer. The loop dereferences only while current < end, so it never reads the one-past pointer.
Measure Distance Between Pointers
#include <stdio.h>
#include <stddef.h>
int main(void)
{
char word[] = "pointer";
char *start = word;
char *letter = word + 3;
char *end = word + 7;
ptrdiff_t offset = letter - start;
ptrdiff_t length = end - start;
printf("Letter: %c
", *letter);
printf("Offset: %td
", offset);
printf("Length without null: %td
", length);
return 0;
}
Output:
Letter: n
Offset: 3
Length without null: 7
Pointer subtraction reports element distance. Because these are char * pointers, the distance is also a byte count, but that is a special property of character arrays. The string literal initializer creates eight array elements: seven visible letters plus the terminating null character. This example sets end to the position after the visible letters.
Use Pointer Arithmetic in a Function
#include <stdio.h>
#include <stddef.h>
void add_to_each(int *values, size_t count, int amount)
{
int *end = values + count;
for (int *p = values; p < end; p++) {
*p += amount;
}
}
void print_values(const int *values, size_t count)
{
const int *end = values + count;
for (const int *p = values; p < end; p++) {
printf("%d", *p);
if (p + 1 < end) {
printf(", ");
}
}
printf("
");
}
int main(void)
{
int scores[] = {81, 87, 90, 76};
size_t count = sizeof scores / sizeof scores[0];
add_to_each(scores, count, 5);
print_values(scores, count);
return 0;
}
Output:
86, 92, 95, 81
When an array is passed to a function, the parameter receives a pointer to the first element. The function therefore also needs count. Both functions build an end pointer and walk from the beginning to that end position.
How It Works Step by Step
- The array declaration creates a contiguous sequence of element objects.
- In an expression such as
values, the array usually converts to a pointer to its first element. - When the program evaluates
values + 3, the compiler computes an address threeintelements after the beginning, not three raw bytes after it. - The dereference operator in
*(values + 3)accesses the object at that computed element position. - A loop such as
p++repeats this scaled movement one element at a time. - The pointer
values + countis allowed as an end marker, but dereferencing it would be out of bounds. - Subtracting two pointers from the same array converts the address difference back into an element count.
The compiler can generate very efficient machine code for these operations because the element size is known from the pointer type. That same type information is why casting pointers casually can break programs: a different pointer type changes how arithmetic and dereferencing are interpreted.
Common Mistakes
Thinking p + 1 Means One Byte
If p is an int *, p + 1 moves by sizeof(int) bytes internally. Correct code thinks in elements: use int *next = p + 1; for the next integer. If you truly need byte movement, use unsigned char *bytes and move that pointer.
Dereferencing the One-Past Pointer
int *end = values + count; is a useful loop boundary, but *end is invalid. The correct loop condition is p < end, followed by *p inside the loop. A condition such as p <= end goes one step too far.
Subtracting Unrelated Pointers
Pointer subtraction is defined only for positions in the same array object. Do not subtract pointers into two different arrays, two separate variables, or two separately allocated blocks. If you need a distance, keep the pointers tied to the same buffer and pass both the beginning and end of that buffer together.
Losing the Start of a Buffer
If you increment the only pointer you have, you may lose the original address needed for later cleanup or for computing an offset. Prefer separate names such as begin, current, and end when a function needs both the original range and the moving position.
Best Practices
- Use pointer arithmetic mainly for arrays, strings, and explicit begin/end ranges.
- Form an end pointer with
array + count, compare against it, and never dereference it. - Use
ptrdiff_tand the%tdformat specifier for pointer differences. - Use
size_tfor element counts and indexes. - Keep pointer arithmetic within one array object, including the legal one-past position.
- Prefer array indexing when an index expresses the idea more clearly.
- Use
constpointers for traversal functions that should read but not modify elements. - Avoid converting pointers to integers for address math; standard pointer arithmetic already handles element scaling.
- Use character pointers for byte-level inspection, not
void *arithmetic.
Practice Exercises
- Write a program that stores six integers and uses
int *beginandint *endto print them in order. - Write a function
size_t count_even(const int *values, size_t count)that uses pointer arithmetic to count even numbers without indexing. - Create a character array containing a short word. Use two pointers to print the characters in reverse order without modifying the array.
Summary
- Pointer arithmetic moves in elements, not raw bytes, based on the pointed-to type.
a[i]is equivalent to*(a + i), which explains how array indexing maps to memory.- The one-past-the-end pointer is legal for comparison but illegal to dereference.
- Pointer subtraction gives an element distance as
ptrdiff_twhen both pointers belong to the same array. - Pointer arithmetic outside a valid array range causes undefined behavior.
- Begin/end pointer ranges are a common, efficient way to process arrays and strings in C.
