Arrays
The first example, below, declares a fixed array of five 64 bit numbers. The .quad specifier indicates 64 bit and the values are specified in the declaration.
.data
array:
.quad 10, 20, 30, 40, 50 // Use .quad to specify 64 bit values
There are two implementations of the process to extract a value at an index. The first is more laborious but the logic is clearer.
// main code
ldr x0, =output
ldr x1, =array // Base address of array
mov x2, #2 // Element pointer, 30
lsl x2, x2, #3 // Multiply x2 by 8 to get the number of addresses to offset
add x1, x1, x2 // Add the offset to the base address
ldr x1, [x1] // Get value at address
bl printf
Ignoring the ldr x0, =output as that is setting up the string to be passed to printf.
X1 is loaded with the base address of the array. This is the address of element 0 in the array, 10 in this case.
Because the array was declared to be of type .quad, each location is eight bytes wide. That is, if the base
address was 0x1000, the element 20 would be stored at 0x1008, the element 30 would be stored at 0x1010.
As there are five elements in the array and each element requires eight bytes, the entire array will be in
40 contiguous memory addresses.
This means that we can find the exact memory address of an element by knowing its index in the array and
multiplying that index by eight then adding that value to the base address to find the address of the required
element.
In the example above the base address is loaded into x1. X2 is then loaded with the immediate value, #2, being the index of the element we want to extract. The next instruction, lsl x2, x2, #3, takes the value in x2, performs a "Logical Shift Left" by the immediate value provided, 3 in this case, and stores the result back in x2. For example, x2 contains 0000 0010. The lsl instruction will shift the bits to the left by three places, appending a zero to the least significant bit each time. The result in x2 will be 0001 0000 or in hex, 0x10
The next line adds the offset in x2 to the base address in x1 to get the address of required element.
In this case: 0x1000 + 0x0010 = 0x1010 which was established earlier.
The final line, ldr x1, [x1] loads x1 with the value stored at the address currently in x1.
The more efficient way of addressing the nth element is using this code:
// main code
ldr x0, =output
ldr x1, =array // Base address of array
mov x2, #2 // Element of interest, 30
ldr x1, [x1, x2, LSL #3 ] // Get value at address
bl printf
The code is the same as the above with one exception, the line ldr x1, [x1, x2, LSL #3]. This mode of addressing
is called Register Offset Addressing. The register, x1, will be loaded with the address in x1
added to the value in x2 once that has been logically left shifted by the immediate value.
Conceptually, it may look like this: x1 <- x1 + (x2, LSL 3 places).
The result is the same in both cases.
// arrays1.s
.global main
.extern printf
.data
array:
.quad 10, 20, 30, 40, 50 // Use .quad to specify 64 bit values
output:
.asciz "Value = %d\n"
.text
main:
// prolog
stp x29, x30, [sp, -16]!
mov x29, sp
// main code
ldr x0, =output
ldr x1, =array // Base address of array
mov x2, #2 // Element pointer, 30
lsl x2, x2, #3 // Multiply x2 by 8 to get the number of addresses to offset
add x1, x1, x2 // Add the offset to the base address
ldr x1, [x1] // Get value at address
bl printf
// Cleanup
mov x0, #0
ldp x29, x30, [sp], 16
RET
// arrays1.s
.global main
.extern printf
.data
array:
.quad 10, 20, 30, 40, 50 // Use .quad to specify 64 bit values
output:
.asciz "Value = %d\n"
.text
main:
// prolog
stp x29, x30, [sp, -16]!
mov x29, sp
// main code
ldr x0, =output
ldr x1, =array // Base address of array
mov x2, #2 // Element of interest, 30
ldr x1, [x1, x2, LSL #3 ] // Get value at address
bl printf
// Cleanup
mov x0, #0
ldp x29, x30, [sp], 16
RET