AArch64 Playground
4.9 · Reading numbers with scanf

Reading numbers with scanf

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 another

A 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      scanf

When 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.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 1try it: run it, or step one instruction at a timeOpen in playground

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:

SpecifierC typeBytes writtenLoad it back with
%dint4a w register
%ldlong8an 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.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 2try it: run it, or step one instruction at a timeOpen in playground

Check yourself

  1. Why does the call pass ldr x1, =first_n and not the value stored at first_n?
  2. A program reads one number with "%ld". How many bytes should its .bss slot reserve, and which kind of register should load it back?
  3. A number read by scanf is printed after another call to printf. Why keep it in w19 rather than w9?

answers

show answers
  1. scanf stores into memory, so it needs the address of the place to store.
  2. 8 bytes, .skip 8, loaded back with an x register.
  3. printf may change x0 to x18, and w9 is the low half of x9, but it leaves x19 to x28 as it found them.

Practice

  • How many minutes old?: read a number into a .bss slot with scanf, the way this lesson does, and scale it.
  • Echo the sum: read two numbers with scanf and print their total. It keeps the numbers in the stack frame rather than in .bss, so each address is made with add instead of ldr =label; the stack lesson explains that form. The call to scanf is the same.
  • Basic quiz: input and output: what scanf needs and returns, and which specifier prints a long. Its questions on svc 0 and write come with the system calls lesson.