Here is what a
and b
look like in memory:
+------------------------------------------+
a | uint8_t* >----------------------+
+------------------------------------------+ |
| +---------+
+---------+ +->| uint8_t | a[0] (or *a)
b[0] | uint8_t | +---------+
+---------+ | uint8_t | a[1] (or *(a+1))
b[1] | uint8_t | +---------+
+---------+ | ... |
b[2] | uint8_t |
+---------+
| ... |
Note that b[1]
, and b[2]
(and possibly b[0]
) are shown as where they would logically reside, even though no actual storage will have been allocated. Also, a
was not initialized, so it may not be pointing at a valid memory location.
Once linked/loaded, the address of a
and b
will be known. These addresses must be loaded into registers in order to access the memory where the variables reside. In ARM, the addresses are stashed as data values, and accessed with pc-relative addressing:
+------------------------------------------+
teste+0 | |
| I n s t r u c t i o n s |
| |
+------------------------------------------+ +------------
teste+14 | uint8_t** >--------------------------> a | uint8_t * ...
+------------------------------------------+ +------------
teste+18 | uint8_t* >------------------------+
+------------------------------------------+ | +---------+
+-> b[0] | uint8_t |
+---------+
b[1] | uint8_t |
+---------+
b[2] | uint8_t |
+---------+
| ... |
By storing these constant addresses at fixed (small) offsets from the instructions, the code can use 16-bit THUMB opcodes to load them into registers. In contrast, MIPS code will typically use a 2 32-bit instruction sequence to accomplish the same thing by loading 16-bit immediates embedded in the instructions into the upper and lower halves of the target register.
Now let's step through the instructions.
0: 4a04 ldr r2, [pc, #16] ; (14 <teste+0x14>)
This line is loading the address of a
(which was stashed in the code after the subroutine) into r2
.
2: 4b05 ldr r3, [pc, #20] ; (18 <teste+0x18>)
This line is loading the address of b[0]
(which was stashed in the code after the subroutine) into r3
.
4: 6811 ldr r1, [r2, #0]
This line is loading the pointer stored in a
into r1
. So, r1
is now pointing at some uint8_t
(the one floating to the right in the diagram).
6: 7858 ldrb r0, [r3, #1]
This line loads a byte from address r3+1
(aka b[1]
) into r0
.
8: 7008 strb r0, [r1, #0]
This stores the byte we just loaded into address r1+0
(aka *a
).
a: 6812 ldr r2, [r2, #0]
This line reloads a
into r2
. This is unnecessary, since we already have this value in r1
; however, I'm guessing you have optimization disabled.
c: 7812 ldrb r2, [r2, #0]
This line loads a byte from address r2+0
(aka *a
) into r2
.
e: 709a strb r2, [r3, #2]
This line stores the byte we just loaded into address r3+2
(aka b[2]
).
10: 4770 bx lr
And finally, we return from the subroutine.