Section 15.1 Arrays
An array in C/C++ consists of one or more elements, all of the same data type, arranged contiguously in memory. An example is the C-style text strings we have been using in the past few chapters. So far, we have been accessing the elements sequentially, one immediately after the other. This array processing only requires the address of the beginning of the array.
To access a single element in an array we need to specify two address-related items:
The beginning of the array.
The number of the element to access.
For example, given the declaration in C:
int array[50];
you can store an integer, say \(123\text{,}\) in the \(i-th\) element with the statement
array[i] = 123;
In this example the beginning of the array is specified by using the name, and the number of the element is specified by the [...]
syntax. The program in Listing 15.1.1 shows how to access each element in an array directly, both storing a value and reading it.
/* printArray1.c * Stores index number in each element of array * and prints the array. * 2017-09-29: Bob Plantz */ #include <stdio.h> int main(void) { int intArray[10]; register int index; for (index = 0; index < 10; index++) intArray[index] = index; for (index = 0; index < 10; index++) printf("%i\n", intArray[index]); return 0; }
Listing 15.1.2 shows a way to access array elements in assembly language.
.arch armv6 .file "printArray1.c" .section .rodata .align 2 .LC0: .ascii "%i\012\000" .text .align 2 .global main .syntax unified .arm .fpu vfp .type main, %function main: @ args = 0, pretend = 0, frame = 40 @ frame_needed = 1, uses_anonymous_args = 0 push {r4, fp, lr} add fp, sp, #8 sub sp, sp, #44 mov r4, #0 @@ index = 0; b .L2 .L3: lsl r3, r4, #2 @@ 4 * index sub r2, fp, #12 @@ address of end of array add r3, r2, r3 @@ index-th element beyond end str r4, [r3, #-40] @@ minus length of array add r4, r4, #1 @@ index++ .L2: cmp r4, #9 @@ check for end ble .L3 mov r4, #0 b .L4 .L5: lsl r3, r4, #2 sub r2, fp, #12 add r3, r2, r3 ldr r3, [r3, #-40] mov r1, r3 ldr r0, .L7 bl printf add r4, r4, #1 .L4: cmp r4, #9 ble .L5 mov r3, #0 mov r0, r3 sub sp, fp, #8 @ sp needed pop {r4, fp, pc} .L8: .align 2 .L7: .word .LC0 .ident "GCC: (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516"
In the code sequence:
mov r3, r4, asl #2 @@ 4 * index sub r2, fp, #12 @@ address of end of array add r3, r2, r3 @@ index-th element beyond end str r4, [r3, #-40] @@ minus length of array
the number of bytes from the beginning of the array to specified element is computed by multiplying the index number by four. Next, the address of the end of the array is computed. The byte offset of the specified element is added to the address of the end of the array, which leaves r3
pointing to an address beyond the end of the array. Storing the value (index number in this program) uses a negative offset equal to the total length (in bytes) of the array. So the net result is that the value is stored in the proper location in the array.
I do not know why the compiler writer decided to use this algorithm to access an array. I have used a more direct approach in Listing 15.1.3
@ printArray2.s @ Stores index number in each element of array @ and prints the array. @ 2017-09-29: Bob Plantz @ Define my Raspberry Pi .cpu cortex-a53 .fpu neon-fp-armv8 .syntax unified @ modern syntax @ Constants for assembler .equ nElements,10 @ number of elements in array .equ intArray,-52 @ array beginning .equ decString,-68 @ for decimal text string .equ locals,56 @ space for local vars @ The program .text .align 2 .global main .type main, %function main: sub sp, sp, 16 @ space for saving regs str r4, [sp, 0] @ save r4 str r5, [sp, 4] @ r5 str fp, [sp, 8] @ fp str lr, [sp, 12] @ lr add fp, sp, 12 @ set our frame pointer sub sp, sp, locals @ for the array add r4, fp, intArray @ address of array beginning mov r5, 0 @ index = 0; fillLoop: cmp r5, nElements @ all filled? bge allFull @ yes lsl r0, r5, 2 @ no, offset is 4 * index str r5, [r4, r0] @ at index-th element add r5, r5, 1 @ index++; b fillLoop allFull: add r4, fp, intArray @ address of array beginning mov r5, 0 @ index = 0; printLoop: cmp r5, nElements @ all filled? bge allDone @ yes lsl r0, r5, 2 @ no, offset is 4 * index ldr r1, [r4, r0] @ get index-th element add r0, fp, decString @ to store decimal string bl uIntToDec @ convert it add r0, fp, decString @ get decimal string bl writeStr @ write it bl newLine add r5, r5, 1 @ index++; b printLoop allDone: mov r0, 0 @ return 0; add sp, sp, locals @ deallocate local var ldr r4, [sp, 0] @ restore r4 ldr r5, [sp, 4] @ r5 ldr fp, [sp, 8] @ fp ldr lr, [sp, 12] @ lr add sp, sp, 16 @ restore sp bx lr @ return
In my algorithm I use r4
as the base register to hold the address of the beginning of the array and r5
to maintain the array element number (index):
add r4, fp, intArray @ address of array beginning mov r5, 0 @ index = 0;
It is then a simple matter to access any element of the array by computing the byte offset from the beginning of the array, storing this value in a register, and using the register addressing mode (Section 11.1):
lsl r0, r5, 2 @ no, offset is 4 * index str r5, [r4, r0] @ at index-th element