xSKILLby Infirals
Register

C for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Pointer arithmetic and array walking

Pointers and memory12 min readLesson 1 of 6

1.1 Moving by elements

p + 1 does not mean the next byte. It means the next object of the pointed-to type. For an int *, adding one moves by sizeof(int) bytes, so the address advances by four where int is four bytes.

The compiler scales every addition and subtraction by the size of the pointed-to type. The same expression works for char *, double *, and pointers to structs without adjustment.

Validity has a second rule. Arithmetic is defined only between pointers into the same array, and one element past the last element counts as part of that range. Anything further is undefined behaviour, even when the address looks harmless.

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

int main(void)
{
    int values[5] = {10, 20, 30, 40, 50};
    int *first = values;
    int *last = values + 4;

    printf("elements between: %td\n", last - first);
    printf("first: %d, last: %d\n", *first, *last);
    printf("step equals sizeof(int): %d\n", ((char *)(first + 1) - (char *)first) == (ptrdiff_t)sizeof(int));
    return 0;
}

Output

elements between: 4
first: 10, last: 50
step equals sizeof(int): 1

1.2 Walking with a pointer

A loop can use a pointer as its state. The usual form keeps one pointer at the first element and compares it with the one-past-the-end address.

Write for (int *it = values; it < values + n; it++) and dereference with *it. Use < when the loop might end early, and != when it must reach the end exactly.

Subscripting works on the loop pointer too, because p[i] is defined as *(p + i). The index counts from it, so it[2] means something different from values[2], which is worth remembering before writing it in a loop.

c
#include <stdio.h>

int main(void)
{
    int values[5] = {3, 1, 4, 1, 5};
    int total = 0;

    for (int *it = values; it < values + 5; it++) {
        total += *it;
    }

    printf("total: %d\n", total);
    printf("third: %d\n", *(values + 2));
    return 0;
}

Output

total: 14
third: 4

1.3 Distance between pointers

Subtracting one pointer from another gives the number of elements between them, not the number of bytes. The result has type ptrdiff_t, a signed integer type from <stddef.h>, and %td prints it.

Both pointers must come from the same array. Subtracting pointers into two different arrays is undefined behaviour, and the number you get is meaningless even when it looks plausible.

The distance is handy in place of a length parameter. A function that walks to a terminator can report how far it travelled by subtracting the start pointer from the place it stopped.

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

static size_t length_of(const char *text)
{
    const char *end = text;
    while (*end != '\0') {
        end++;
    }
    return (size_t)(end - text);
}

int main(void)
{
    printf("%zu\n", length_of("pointer"));
    printf("%zu\n", length_of(""));
    return 0;
}

Output

7
0

1.4 Two pointers, one array

A pair of pointers can work from both ends of an array. The loop stops when the pointers meet or cross, so the body never needs the index.

Reversing in place is the standard example. left starts at the first element and right at the last. Each pass swaps the two values, then moves both pointers inward.

When the count is odd, the middle element is reached by both pointers and the loop ends. Nothing special is needed for that case.

c
#include <stdio.h>

int main(void)
{
    int values[5] = {1, 2, 3, 4, 5};
    int *left = values;
    int *right = values + 4;

    while (left < right) {
        int temp = *left;
        *left = *right;
        *right = temp;
        left++;
        right--;
    }
    for (int i = 0; i < 5; i++)
        printf(i == 0 ? "%d" : " %d", values[i]);
    printf("\n");
    return 0;
}

Output

5 4 3 2 1

1.5 The one-past-the-end address

For an array of n elements, values + n is legal to compute and legal to compare. It must never be dereferenced, because it does not point at an object.

That single allowed address makes half-open ranges work. The range [begin, end) includes begin and stops before end, so while (it != end) visits every element once and end - begin is exactly n.

One step further, values + n + 1, is undefined even when you never dereference it. The compiler may assume the expression never occurs and optimise around that assumption.

c
#include <stdio.h>

static int sum_range(const int *begin, const int *end)
{
    int total = 0;
    for (const int *it = begin; it != end; it++) {
        total += *it;
    }
    return total;
}

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

Output

20
4

1.6 Array parameters lose their size

When an array goes to a function, the parameter becomes a pointer. sizeof inside the function reports the size of that pointer, so the loop limit from main no longer works.

Pass the count as a second parameter, or pass a pointer to the end. The C standard library usually pairs a pointer with a count. Iterator-style code pairs two pointers.

Inside main, where the array is still an array, sizeof values / sizeof values[0] gives the element count. That trick stops working the moment the array decays to a pointer, which is why passing the count is the reliable habit.

c
#include <stdio.h>

static int total(const int *values, int count)
{
    int sum = 0;
    for (int i = 0; i < count; i++) {
        sum += values[i];
    }
    return sum;
}

int main(void)
{
    int values[6] = {1, 1, 2, 3, 5, 8};
    int count = (int)(sizeof values / sizeof values[0]);

    printf("count: %d\n", count);
    printf("total: %d\n", total(values, count));
    return 0;
}

Output

count: 6
total: 20

1.7 Reading const data with a moving pointer

A const int * can still move. The const applies to the pointed-to data, so p++ is allowed while *p = 0 is not. That combination covers most read-only walks.

A function parameter that only reads should be declared const. The caller can pass a plain array, and the compiler rejects an accidental write inside the function.

The const has to sit on the side that matters. const int * protects the data, and int *const protects the pointer. Moving the keyword changes which part is locked.

c
#include <stdio.h>

static int count_above(const int *values, int count, int limit)
{
    int found = 0;
    for (const int *it = values; it < values + count; it++)
        if (*it > limit)
            found++;
    return found;
}

int main(void)
{
    int scores[5] = {55, 72, 90, 61, 84};
    printf("above 70: %d\n", count_above(scores, 5, 70));
    return 0;
}

Output

above 70: 3

1.8 Mistakes that compile

Pointer arithmetic is weakly checked. These mistakes build cleanly and fail at run time:

  • subtracting pointers that point into different arrays
  • dereferencing values + count, the one-past-the-end address
  • comparing pointers from two arrays with <
  • keeping a pointer into an array after the array has gone out of scope
  • writing *ptr++ when you meant (*ptr)++

A sanitizer build catches many of them. With GCC or Clang, -fsanitize=address,undefined turns an out-of-bounds read into a report with a line number. MSVC offers /fsanitize=address for the address checks.

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01Why does adding 1 to an `int *` move by more than one byte?

    The compiler scales pointer arithmetic by the size of the pointed-to type. `p + 1` means the next `int`, which sits `sizeof(int)` bytes away.

  • 02What is `ptrdiff_t`, and why print it with `%td`?

    It is the signed integer type produced by subtracting two pointers. `%td` is the matching conversion, so the value prints correctly on both 32-bit and 64-bit builds.

  • 03Is `values + 5` allowed for an array of five ints?

    Yes, as a value to compare or subtract. It points one past the last element, and it must not be dereferenced.

Quiz

Check: pointer arithmetic

Four questions on how pointers move, subscripting, pointer differences and the valid range of arithmetic.

4 questions

Question 1 of 4

0/4 answered

For an int pointer on a machine where int is four bytes, adding 1 advances the address by:

Answer all you can, then submit.