Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 12

Pointer Traversal of Arrays

Recommended resources

Hello! Welcome to your next lesson in the "Pointer Arithmetic and Arrays" module.

In our last session, we explored pointer subtraction, learning how ptr2 - ptr1 gives us the number of elements between two pointers within the same array. This is a vital tool for calculating ranges and buffer sizes. We saw how this operation is implicitly scaled by the size of the data type, a detail crucial for writing portable code.

Today, we'll shift from calculating static distances to dynamic movement. Our goal is to master the technique of traversing arrays using pointers instead of index variables. We will combine our knowledge of pointer arithmetic and array-pointer equivalence to build efficient and idiomatic C loops. This skill is fundamental for manipulating data buffers, implementing algorithms, and understanding a vast amount of existing C code, particularly in embedded systems and communication stacks where direct memory handling is routine.

The "Moving Pointer" Paradigm

You're already comfortable with the equivalence of arr[i] and *(arr + i). While the latter uses pointer arithmetic, it still relies on an index i. True pointer-based traversal involves modifying the pointer itself to "walk" through the array's elements.

The core idea is simple:

  1. Initialize a pointer to the beginning of the array.
  2. In a loop, dereference the pointer to access the current element.
  3. Increment the pointer to move it to the next element.
  4. Stop when the pointer has passed the last element.

A common mistake is to manually add the size of the data type to the pointer. Let's watch a video that clarifies why this is wrong and what the compiler does for you automatically. The presenter also looks at the resulting assembly code, which should be interesting given your background.

you will never ask about pointer arithmetic after watching this video

This video from the 'Low Level' channel explains the most critical aspect of pointer-based traversal: the compiler automatically scales the increment. It highlights a common pitfall and shows the resulting machine code.

Watch from 02:35 to 04:31 and then from 05:23 to 07:05. Pay close attention to why p_person + 1 is correct, while p_person + sizeof(struct person) is incorrect. Note the add instruction in the assembly dump, which adds the correct byte offset (0x44, or 68 bytes) automatically.

As the video demonstrates, when you write ptr++, the compiler knows the type ptr points to and adds the correct number of bytes (sizeof(*ptr)) to advance the pointer to the next element. This automatic scaling is what makes pointer arithmetic both powerful and clean.


Implementing Pointer-Based Traversal

Let's see how this works in practice with a standard for loop.

Standard Index-Based Loop

Here is a familiar loop that sums the elements of an array using an index:

#define SIZE 5

int array[SIZE] = {10, 20, 30, 40, 50};
int sum = 0;

for (int i = 0; i < SIZE; i++) {
    sum += array[i];
}
// sum is now 150

Pointer-Based Loop

Now, let's rewrite this using a moving pointer. A common and robust idiom is to create a second pointer that marks the end of the traversal range.

#define SIZE 5

int array[SIZE] = {10, 20, 30, 40, 50};
int sum = 0;

int *ptr;
int * const end_ptr = array + SIZE; // Pointer to one element PAST the end. A const pointer is good practice.

for (ptr = array; ptr < end_ptr; ptr++) {
    sum += *ptr;
}
// sum is again 150

Let's break this down:

  • int *ptr;: Declares our iterator pointer.
  • int * const end_ptr = array + SIZE;: Declares a const pointer that points one element past the end of the array. This is a valid address for comparison but must not be dereferenced. Using const prevents it from being accidentally modified.
  • ptr = array;: The initialization sets our iterator ptr to point to the first element of array.
  • ptr < end_ptr;: The condition checks if our iterator has reached the end. This is a safe comparison.
  • ptr++: The increment moves ptr to the next integer in memory.
  • *ptr: Inside the loop, we dereference ptr to get the value of the element it currently points to.

The following image provides a fantastic visualization, connecting the C code for a pointer-based loop to the memory layout and the underlying assembly instructions.

Array Indexing vs. Pointer: Pointer-based Array Traversal
This diagram illustrates a function `clear2` that uses a pointer (`current`) to traverse and zero-out an integer array. It shows the C code, the corresponding MIPS assembly, and a memory diagram. Notice in the assembly how the `add` instruction increments the pointer register by 4 bytes in each iteration, corresponding to `current++` in C for an `int*`.

Practical Demonstration

Let's watch one more short video that walks through traversing both a simple integer array and an array of structs using pointer increments. This will solidify your understanding of how the concept applies universally regardless of the data type.

Master Pointer Arithmetic In 10 Minutes (The Ultimate Guide For C Programmers).

This video from HuwsTube demonstrates pointer arithmetic to traverse an array, clearly showing the pointer value (address) changing with each increment.

Watch from 01:00 to 03:02 and from 08:12 to 09:10. The first part covers a simple integer array, and the second part proves that the exact same logic works for a more complex array of structs, reinforcing the type-scaling principle.

For a comprehensive textual reference on pointer arithmetic and its relationship with arrays, the following resource is excellent.

Chapter 8: Pointers and Memory Allocation

The 'Learning C with Pebble' guide provides a clear, written explanation of pointer arithmetic and the interchangeability of pointer and array notation.

Read the sections 'Pointer Arithmetic', 'Pointers and Arrays', and 'Pointer Arithmetic and Array Indicies'. These sections formalize the concepts we've discussed, showing how *(bigspace + 1) is equivalent to bigspace[1] and how ps++ moves the pointer to the next element regardless of whether it's a short* or long*.


An Alternative: The while Loop

The for loop is explicit and clear, but you will often encounter a more compact while loop pattern, especially when processing C-style strings (which are null-terminated character arrays).

This pattern cleverly combines dereferencing, checking the value, and incrementing the pointer.

char message[] = "Hello";
char *c_ptr = message;

// This loop continues until *c_ptr is the null terminator ('\0'), which evaluates to false.
while (*c_ptr) {
    printf("%c ", *c_ptr);
    c_ptr++;
}
// Output: H e l l o

This can be made even more compact using the post-increment operator:

char *c_ptr = message;
while (*c_ptr) {
    printf("%c ", *c_ptr++); // 1. Use the value *c_ptr, then 2. increment c_ptr
}

This concise style is very common in C and is something we will explore more in the next lesson.

Test your understanding!

You have a function that finds the maximum value in an array using index notation. Rewrite it to use pointer-based traversal. Do not use the [] operator in your new implementation.

Original Function:

int find_max_by_index(int *arr, int size) {
    if (size <= 0) return -1; // Or some other error indication

    int max_val = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] > max_val) {
            max_val = arr[i];
        }
    }
    return max_val;
}

Your Task: Implement find_max_by_pointer

int find_max_by_pointer(int *arr, int size) {
    if (size <= 0) return -1;

    // Your implementation here using a moving pointer
    
}
Show answer

Here is one possible implementation:

int find_max_by_pointer(int *arr, int size) {
    if (size <= 0) return -1;

    int max_val = *arr; // Initialize max_val with the first element's value
    int *ptr = arr + 1; // Start the loop from the second element
    int * const end_ptr = arr + size;

    for (; ptr < end_ptr; ptr++) {
        if (*ptr > max_val) {
            max_val = *ptr;
        }
    }
    return max_val;
}

Explanation:

  1. We initialize max_val by dereferencing the base pointer arr.
  2. The iterator pointer ptr is initialized to arr + 1 to start the comparison from the second element.
  3. The loop continues as long as ptr is within the array bounds (ptr < end_ptr).
  4. Inside the loop, *ptr is compared with max_val, and max_val is updated if a larger element is found.

Conclusion

In this lesson, we've transitioned from accessing array elements via offsets to actively traversing them with a moving pointer. This is a cornerstone of idiomatic C programming.

Key Takeaways:

  • Arrays are traversed by initializing a pointer to the start and incrementing it in a loop.
  • The loop condition should compare the iterator pointer against a pointer to one element past the end of the array (e.g., ptr < arr + size).
  • The C compiler automatically handles the scaling of pointer increments (ptr++), making the code clean and type-independent.
  • This technique is not just an alternative to indexing; it's a fundamental pattern for low-level data manipulation, especially with strings and raw data buffers.

In our next and final lesson of this module, we will apply these traversal techniques specifically to C strings, learning how to implement standard string library functions from scratch using pointer arithmetic.