This topic presented a lot of sophisticated concepts. It's unfortunate that one of the more complex topics in ARM assembly needs to be introduced early, but we need to use memory, and ldr and str are the only ways. Spend the time to think it all through carefully, and review code examples as needed. Here's a summary:
All operations on data are done on registers, so you must copy data to/from memory and registers to get things done. The ldr and str instructions are how you do this. ldr copies from memory into a register, and str does the reverse, copying from a register into memory. Both instructions copy between a register and a memory location, not e.g. between two memory locations or between two registers. (For copying between registers, simply use a mov)
Every byte in memory has a unique 32-bit address, starting from 0. This is an entirely different number from its contents. The address tells where the data is; the contents tell what the data is.
Only instructions, and data directives (e.g. .word or .ascii) occupy memory space and have address locations. (Also, .align directives may occupy space if they need to add padding bytes to get the next data into 4-byte alignment.) All other assembly directives do not add to memory content. Neither do labels.
Labels in the assembly code represent memory addresses. Each label "stands for" the address at which it's declared.
Getting data to/from memory requires specifying the address of the data, but there isn't enough room in an ldr or str instruction for both the 32-bit address and the opcode, target register, etc. So, ldr and str always rely on an address register, a register that holds an address in memory. This is different from the target register where the data is being loaded into or stored from.
The address register appears in [] in the ldr and str instructions. So, it's ldr targetRegister, [addressRegister]. I'll abbreviate that to ldr TR, [AR] from now on.
There is enough room in the ldr and str instruction to hold an additional address offset, between ldr TR, [AR, #offset]
In particular, it's often useful to use the PC, which holds the address of the ldr or str instruction itself, as the address register: ldr TR, [PC, #offset]. This is always done with some address offset (otherwise we'd be loading or storing the instruction itself into a data register, which is not useful). But, with a suitable offset, we can load or store any data that is within
The assembler allows a shorthand pseudoinstruction of the form ldr TR, LABEL or str TR, LABEL. This translates to a real ldr or str instruction that uses pc-relative addressing, e.g. ldr RT, [PC, #offset], with whatever offset is needed to get to the data at that label.
Many assembly programs include preloaded data that is reasonably close to the code, to be loaded via pc-relative addressing.
The PC, as well as the FP and SP registers we'll look at later, always contain addresses. Most but not all ldr and str instructions use those registers as address registers.
Getting an address into any other register, so we can use it as an address register, takes some doing. The only way to do this is to put the desired address itself in memory, at a pc-relative location, so we can load the address into a register via pc-relative addressing. We can do this by hand, as we did for numStrLoc, using the desired label to represent the address in a .word directive, e.g. numStrLoc: .word numStr.
OR, we can get the assembler to do this legwork for us, automatically adding an address-holding word at a pc-relative location and loading it via pc-relative addressing. The assembler does this for you if you add = ahead of the label, e.g. ldr RT, =numStr. To be clear, =numStr means the address represented by numStr, not the data at numStr.
Code goes in a text or "code" memory segment, declared via .text. This segment is marked readonly by the MMU. This prevents accidental modification of the code, and also allows different programs to share the same code without interference.
Data that is near enough to be reached by pc-relative addressing is also in the readonly text segment, and thus can be read and used, but not modified (written to with str).
We may declare a read/write data memory segment, via directive .data. A data segment holds only data directives, not code. And it will be at memory location far from the text segment, so its content is not reachable by pc-relative addressing, even if it looks that way in the assembly listing.
ldr and str offer automatic increment/decrement of the address register, along with offsetting. To use an offset and also change the address register by that offset, write ldr RT, [RA, #offset]! To use the address register without offset, but add an offset to it after the load or store, use ldr RT, [RA], #offset.