Reading numbers with scanf
prerequisite
Read these first:
scanf is the input side of printf. It reads text typed at the keyboard, turns it into numbers as its format string describes, and stores each number in memory. This lesson reserves memory for the numbers, calls scanf, and loads what it stored into registers.
scanf needs addresses
printf only reads the values it prints, so you pass it the values. scanf has to change variables, so you pass it their addresses: the format string's address in x0, then the address of the first variable in x1, the second in x2, and so on. scanf writes each number it reads to the matching address.
Passing a value where an address belongs is the most common scanf mistake. scanf then treats the number as an address and writes wherever it points, which on Linux usually ends the program with a segmentation fault: the operating system stopping a program that touched memory it does not own.
Room for the numbers: .bss
The numbers need a place in memory to land. The .bss section holds variables that start out as zero. The program file does not store their contents, only how many bytes each one needs, and every byte is zero when the program starts. .skip 4 reserves 4 bytes, the size of one int:
.bss .balign 4first_n: .skip 4 // room for one intsecond_n: .skip 4 // and room for anotherA label in .bss works like a label in .data: ldr x1, =first_n puts its address in x1.
Calling scanf
The format string "%d %d" asks for two int values. scanf skips spaces and line breaks before each number, so 12 30 on one line and 12 and 30 on two lines both work. The call needs three arguments:
ldr x0, =fmt_two // what to read: "%d %d" ldr x1, =first_n // where the first number goes ldr x2, =second_n // where the second number goes bl scanfWhen scanf returns, w0 holds how many numbers it stored. With good input that is 2 here; it is smaller when the input stops early or is not a number.
Loading the numbers
After the call, each number sits in memory, not in a register. Load it the same way as any variable: the address first, then the value.
A number that must still be there after the next printf or scanf goes in one of w19 to w28. A library function is allowed to change x0 to x18, but it leaves x19 to x28 as it found them. The program below prints twice, so it keeps the two numbers in w19 and w20.
The lesson feeds it the input 12 30, so it prints:
12 plus 30 is 42
12 times 30 is 360
To try your own numbers, open it in the playground, run it, and type two numbers in the console when it waits for input.
note
This main uses w19 and w20 without saving those registers first. Course programs do this in main: when main returns, the program ends, so nothing is left to notice the change. A function of your own must save any of x19 to x28 that it changes and put them back before it returns, which a later lesson shows.
Match the specifier to the slot
Each specifier tells scanf how many bytes to write at the address you pass:
| Specifier | C type | Bytes written | Load it back with |
|---|---|---|---|
%d | int | 4 | a w register |
%ld | long | 8 | an x register |
The slot needs room for all of those bytes, and the load that reads it back must be the same size. A %ld aimed at a 4-byte slot writes 8 bytes and wipes out whatever is stored just after the slot. A %d aimed at an 8-byte slot fills only 4 of its 8 bytes, and an 8-byte load reads the other 4 as well.
The program below reads a long with %ld into an 8-byte slot and an int with %d into a 4-byte slot. The lesson feeds it -5 -7, and it prints long: -5, int: -7.
Now make the mistake on purpose: change "%ld %d" to "%d %d" and run it again. It prints long: 4294967291, int: -7. %d wrote the 4 bytes of -5 into the first half of the slot, the second half kept the zeros that .bss starts with, and the 8-byte load read the two halves as one large positive number. A positive input hides the mistake, because the missing half would have been zeros anyway.
%ld is also what reads a number too big for an int. Open the program in the playground and type 5000000000 -7: it prints long: 5000000000, int: -7.
Check yourself
- Why does the call pass
ldr x1, =first_nand not the value stored atfirst_n? - A program reads one number with
"%ld". How many bytes should its.bssslot reserve, and which kind of register should load it back? - A number read by
scanfis printed after another call toprintf. Why keep it inw19rather thanw9?
answers
show answers
scanfstores into memory, so it needs the address of the place to store.- 8 bytes,
.skip 8, loaded back with anxregister. printfmay changex0tox18, andw9is the low half ofx9, but it leavesx19tox28as it found them.
Practice
- How many minutes old?: read a number into a
.bssslot withscanf, the way this lesson does, and scale it. - Echo the sum: read two numbers with
scanfand print their total. It keeps the numbers in the stack frame rather than in.bss, so each address is made withaddinstead ofldr =label; the stack lesson explains that form. The call toscanfis the same. - Basic quiz: input and output: what
scanfneeds and returns, and which specifier prints along. Its questions onsvc 0andwritecome with the system calls lesson.