Pointer Arithmetic & Arrays

C · Lesson 19 of 32 7 min read Updated

In C, arrays and pointers are closely linked. When you pass an array to a function, write marks[i], or loop through a string, pointers are working underneath. Understanding this link explains several C rules that otherwise seem strange, such as why sizeof behaves differently inside a function.

In this lesson you will learn how an array name turns into a pointer, how adding to a pointer moves it from element to element, how to subtract pointers, and how to work with arrays of strings.

An array name becomes a pointer

In almost every expression, the name of an array is converted to a pointer to its first element. This conversion is often called decay. So int *p = marks; means exactly the same as int *p = &marks[0];.

Pointer arithmetic then lets you move through the array. Adding 1 to a pointer does not add 1 byte; it moves to the next element, so the address grows by sizeof one element. For an int array, that is 4 bytes.

C
#include <stdio.h>

int main(void)
{
    int marks[4] = {78, 85, 91, 66};
    int *p = marks;   // same as &marks[0]

    for (int i = 0; i < 4; i++) {
        printf("p + %d = %p, *(p + %d) = %d, marks[%d] = %d\n",
               i, (void *) (p + i), i, *(p + i), i, marks[i]);
    }
    return 0;
}

A typical run on 64-bit Linux prints:

Output
p + 0 = 0x7ffd2a6c3f30, *(p + 0) = 78, marks[0] = 78
p + 1 = 0x7ffd2a6c3f34, *(p + 1) = 85, marks[1] = 85
p + 2 = 0x7ffd2a6c3f38, *(p + 2) = 91, marks[2] = 91
p + 3 = 0x7ffd2a6c3f3c, *(p + 3) = 66, marks[3] = 66

Your addresses will differ, but they always go up by 4 (in hexadecimal, ...30, ...34, ...38, ...3c). For a double array they would go up by 8. The output also shows the key rule of C: a[i] is defined as *(a + i). The square-bracket syntax is just a convenient way of writing pointer arithmetic.

Walking through an array with a pointer

Instead of an index, you can move a pointer from the first element to the end. A pointer to the position just after the last element is allowed as an end marker, as long as you never dereference it.

C
#include <stdio.h>

int total(const int *begin, const int *end)
{
    int sum = 0;
    for (const int *q = begin; q < end; q++) {
        sum += *q;
    }
    return sum;
}

int main(void)
{
    int sales[] = {1200, 850, 1430, 990, 1530};
    size_t n = sizeof sales / sizeof sales[0];

    printf("Week total: Rs %d\n", total(sales, sales + n));
    printf("First three days: Rs %d\n", total(sales, sales + 3));
    return 0;
}
Output
Week total: Rs 6000
First three days: Rs 3480

sales + n points one past the end, so the loop stops just in time. Passing a begin and an end pointer makes it easy to work on part of an array, as the second call shows. Both index loops and pointer loops are correct; use whichever is clearer.

You will often see the idiom *p++. Postfix ++ binds more tightly than *, so *p++ means "use the value p points to, then move p to the next element".

Subtracting pointers

Subtracting two pointers into the same array gives the number of elements between them. The result has the type ptrdiff_t (from stddef.h), printed with %td. This is how you turn a found position back into an index.

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

size_t my_strlen(const char *s)
{
    const char *p = s;
    while (*p != '\0') {
        p++;
    }
    return (size_t) (p - s);
}

int main(void)
{
    const char *city = "Thiruvananthapuram";
    printf("%s has %zu letters\n", city, my_strlen(city));

    int seats[10] = {};
    int *first = &seats[2];
    int *last = &seats[7];
    ptrdiff_t gap = last - first;
    printf("Elements from seat 2 to seat 7: %td\n", gap);
    return 0;
}
Output
Thiruvananthapuram has 18 letters
Elements from seat 2 to seat 7: 5

my_strlen moves p to the terminating '\0', and p - s is the number of characters passed. This is essentially how the real strlen works. Subtracting or comparing pointers into different arrays is undefined behaviour, so only do it within one array.

Arrays and pointers are not identical

Decay makes arrays and pointers look the same, but they are different things:

  • An array is a block of elements; a pointer only holds an address.
  • sizeof an array gives the size of all its elements; sizeof a pointer gives 8 on 64-bit systems.
  • You can make a pointer point somewhere else (p = other;), but you cannot assign to an array name.

A function parameter declared as an array is really a pointer. That is why the length must be passed separately:

C
#include <stdio.h>

void show_size(int values[])   // really: int *values
{
    printf("Inside the function: %zu bytes\n", sizeof values);
}

int main(void)
{
    int values[4] = {1, 2, 3, 4};
    printf("In main: %zu bytes\n", sizeof values);
    show_size(values);
    return 0;
}
Output
In main: 16 bytes
Inside the function: 8 bytes

GCC even warns that sizeof on an array parameter returns the size of int *. The output shown is from a 64-bit system.

Arrays of strings

An array of const char * holds one pointer per string, which is the usual way to store a list of fixed words or names:

C
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char *stations[] = {"Delhi", "Agra", "Gwalior", "Bhopal"};
    size_t n = sizeof stations / sizeof stations[0];

    for (size_t i = 0; i < n; i++) {
        printf("Stop %zu: %s (%zu letters, starts with %c)\n",
               i + 1, stations[i], strlen(stations[i]), stations[i][0]);
    }
    return 0;
}
Output
Stop 1: Delhi (5 letters, starts with D)
Stop 2: Agra (4 letters, starts with A)
Stop 3: Gwalior (7 letters, starts with G)
Stop 4: Bhopal (6 letters, starts with B)

stations[i] is a pointer to one string, and stations[i][0] is that string's first character. The strings themselves are read-only literals. If you need to change the text, use a two-dimensional char array instead, such as char names[4][20];, where each row is a 20-character buffer you own.

Reversing an array with two pointers

Two pointers moving towards each other solve many array problems neatly. Here end starts one past the last element:

C
#include <stdio.h>

void reverse(int *begin, int *end)
{
    while (begin < end) {
        end--;
        int temp = *begin;
        *begin = *end;
        *end = temp;
        begin++;
    }
}

int main(void)
{
    int runs[] = {10, 20, 30, 40, 50};
    size_t n = sizeof runs / sizeof runs[0];

    reverse(runs, runs + n);
    printf("Reversed:");
    for (size_t i = 0; i < n; i++) {
        printf(" %d", runs[i]);
    }
    printf("\n");
    return 0;
}
Output
Reversed: 50 40 30 20 10

Common mistakes

  • Going past one-past-the-end. You may point one element past the end, but not further, and you may never dereference that end pointer.
  • Using sizeof on an array parameter. It gives the pointer size. Pass the length or an end pointer.
  • Comparing pointers from different arrays. Only compare or subtract pointers within the same array.
  • Arithmetic on void *. Standard C does not allow p + 1 on a void *, because the element size is unknown. Convert to a typed pointer first.
  • Misreading *p++. It moves the pointer, not the value. To increment the value, write (*p)++.

Key points

  • An array name decays to a pointer to its first element: int *p = arr;.
  • p + 1 moves by one element, not one byte, and a[i] means *(a + i).
  • A pointer one past the last element is a valid end marker but must not be dereferenced.
  • Subtracting pointers in one array gives the element distance as a ptrdiff_t (print with %td).
  • Array parameters are pointers, so sizeof inside the function gives the pointer size.
  • const char *names[] stores a list of read-only strings; use char names[n][len] for editable text.

Exercise

Write a function int *find_value(int *begin, int *end, int target) that returns a pointer to the first element equal to target, or nullptr if there is none. In main, search the roll numbers 104, 117, 121, 133 and 140 for 121 and for 150, and use pointer subtraction to print the index of a match.

Show solution
C
#include <stdio.h>

int *find_value(int *begin, int *end, int target)
{
    for (int *p = begin; p < end; p++) {
        if (*p == target) {
            return p;
        }
    }
    return nullptr;
}

int main(void)
{
    int rolls[] = {104, 117, 121, 133, 140};
    size_t n = sizeof rolls / sizeof rolls[0];
    int targets[] = {121, 150};

    for (int i = 0; i < 2; i++) {
        int *hit = find_value(rolls, rolls + n, targets[i]);
        if (hit != nullptr) {
            printf("%d found at index %td\n", targets[i], hit - rolls);
        } else {
            printf("%d not found\n", targets[i]);
        }
    }
    return 0;
}
Output
121 found at index 2
150 not found

hit - rolls counts the elements between the start of the array and the match, which is exactly its index.

Quick Quiz

1. If int a[5] starts at address 1000 and int is 4 bytes, what address is a + 3?

2. Which expression is always the same as arr[i]?

3. What does sizeof give for an array parameter inside a function on a 64-bit system?

4. What is the type of the difference between two pointers into the same array?

Learn C with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call