Why This Comparison Matters
In high-level C code, walking through an array using index notation and walking through it using a pointer both look natural and often produce the same logical result. But a compiler translates each style into a distinct pattern of RISC-V instructions, and the resulting hardware work is not identical. Understanding this difference is a common source of insight into why certain coding styles run faster in practice.
The Array-Indexing Approach
Consider a simple loop that clears every element of an array using index notation:
void clear1(long array[], long size) {
for (long i = 0; i < size; i += 1) {
array[i] = 0;
}
}To translate array[i] into a memory access, the processor must, on every single loop iteration, recompute the memory address by multiplying the index i by the size of each element, then adding that result to the array's base address:
Loop:
bge i, size, Exit
slli t0, i, 3
add t1, array, t0
sd x0, 0(t1)
addi i, i, 1
jal x0, Loop
Exit:Notice the extra slli (shift left, used here to multiply by 8 bytes) and add instructions needed on every iteration purely to compute the address from the index.
The Pointer-Based Approach
Now consider the same logic written using pointer arithmetic instead of indexing:
void clear2(long *array, long size) {
long *p;
for (p = &array[0]; p < &array[size]; p = p + 1) {
*p = 0;
}
}Here, the compiler can generate a loop where the pointer itself is directly incremented by a fixed amount each iteration, without ever recalculating an address from an index:
Loop:
bge p, end_p, Exit
sd x0, 0(p)
addi p, p, 8
jal x0, Loop
Exit:This version eliminates the multiplication step entirely, since the pointer already holds a real memory address and only needs a simple fixed addition to move to the next element.
Why the Difference Exists
An array index is just a number that must be converted into an address every time it is used, requiring a multiplication by the element size on each access. A pointer, in contrast, already is an address, so advancing it to the next element only requires adding the element's fixed size once, rather than recomputing a product from scratch.
Practical Takeaway
Modern optimizing compilers are often capable of automatically transforming index-based loops into pointer-based ones internally, a process called Strength Reduction, which can eliminate this performance gap without the programmer needing to rewrite the code manually. However, understanding the underlying hardware difference explains why, historically, and in cases where a compiler cannot safely perform this optimization, pointer-based iteration has been recommended for performance-sensitive code.