Read an integer array, compute its sum and maximum with loops in x86-64 assembly (Intel syntax), and print the result by calling scanf and printf.
Updated on
Exam Code Assembly Programming fundamentals Exam level Tested code

A controller board on a factory machine logs the change in power draw once per minute, in watts. Each reading is a signed integer: a positive value means consumption went up, a negative one means it went down. Write a small program in x86-64 assembly (GNU as, .intel_syntax noprefix) that stores the readings in an array and then analyses them.
Input (standard input): an integer n with 0 ≤ n ≤ 1000, followed by n integers separated by spaces or newlines. Each one lies in the range [-1000000000, 1000000000].
Output: if n is 0, the program prints the line Empty array. Otherwise it prints two lines. The first is Sum: S, where S is the sum of all readings. The second is Max: M (index P), where M is the largest reading and P its position, counted from 0. If the maximum appears more than once, P is the index of its first occurrence. Every line ends with a newline.
Requirements: the program defines main in a single file main.s and is linked with gcc -no-pie. Input is read with scanf and output written with printf. The elements must first be stored in an array of 1000 64-bit integers reserved in .bss. They are then traversed by a second loop that computes the sum and the maximum. You must follow the System V AMD64 calling convention, including 16-byte stack alignment at every call.
A sample input and its expected output are shown below.
Sample input
6
4 -2 9 3 9 -5Expected output
Sum: 18
Max: 9 (index 2)- Reading n and the n elements into the array with scanf, passing the correct address of each slot (scale 8).
- 0.75
- Traversal loop: 64-bit running sum, maximum with a signed comparison, and the index of its first occurrence.
- 1.0
- System V calling convention: arguments in rdi, rsi and rdx, eax zeroed for variadic calls, stack 16-byte aligned at every call, callee-saved registers preserved.
- 0.5
- Empty-array case and exact output format.
- 0.25
Hints
Hint 1 · Which address does scanf receive on each iteration?
scanf("%ld", p) needs the address to write to in rsi, not a value. If each element takes 8 bytes, element i lives at base + i*8. The addressing mode [base + index*8] combined with lea computes that address without any manual multiplication.
Hint 2 · Which registers survive a call?
scanf and printf may clobber rax, rcx, rdx, rsi, rdi and r8–r11. The read-loop counter and the value of n must live in registers the callee is obliged to preserve: rbx and r12–r15. If you use them, save them with push on entry to main, then count how many bytes the stack holds afterwards.
Hint 3 · What value should the maximum start with?
Starting the maximum at 0 fails when every reading is negative. Instead, take the first element as the initial sum and maximum, with index 0, and loop from i = 1. To keep the first occurrence, update only when the new value is strictly greater. Use a signed jump as well (jle/jg), not jbe/ja.
Solution
Explained solution
The solution has three parts. A prologue saves registers and leaves the stack aligned. A first loop reads the n elements into vec. A second loop walks the array, accumulating the sum and tracking the maximum. The case n = 0 branches early to the label empty_vec.
Every value that must survive a libc call sits in a callee-saved register. rbx holds n. r12 holds the read index and later the sum. r13 holds the maximum. r14 holds the array base and later the index of the maximum.
.intel_syntax noprefix
.section .rodata
fmt_in: .string "%ld"
fmt_sum: .string "Sum: %ld\n"
fmt_max: .string "Max: %ld (index %ld)\n"
msg_empty: .string "Empty array\n"
.bss
.align 8
n: .skip 8 # number of elements
vec: .skip 8000 # 1000 64-bit integers
.text
.globl main
main:
push rbp # rsp is now 16-byte aligned
mov rbp, rsp
push rbx # 4 pushes of 8 bytes = 32 bytes:
push r12 # 16-byte alignment is preserved
push r13
push r14
# scanf("%ld", &n)
lea rdi, [rip + fmt_in]
lea rsi, [rip + n]
xor eax, eax
call scanf
cmp eax, 1
jne empty_vec
mov rbx, [rip + n] # rbx = n
test rbx, rbx
jle empty_vec
# Read loop: for (i = 0; i < n; i++) scanf("%ld", &vec[i])
xor r12d, r12d # r12 = i = 0
read_loop:
cmp r12, rbx
jge end_read
lea rdi, [rip + fmt_in]
lea rax, [rip + vec]
lea rsi, [rax + r12*8] # rsi = &vec[i]
xor eax, eax
call scanf
inc r12
jmp read_loop
end_read:
# Scan loop: sum and maximum
lea r14, [rip + vec] # r14 = &vec[0]
mov r12, [r14] # sum = vec[0]
mov r13, r12 # max = vec[0]
xor edx, edx # maxidx = 0
mov ecx, 1 # i = 1
scan_loop:
cmp rcx, rbx
jge end_scan
mov rax, [r14 + rcx*8] # rax = vec[i]
add r12, rax # sum += vec[i]
cmp rax, r13
jle no_max # signed; strict: keeps first occurrence
mov r13, rax # max = vec[i]
mov rdx, rcx # maxidx = i
no_max:
inc rcx
jmp scan_loop
end_scan:
mov r14, rdx # maxidx into a register that survives printf
# printf("Sum: %ld\n", sum)
lea rdi, [rip + fmt_sum]
mov rsi, r12
xor eax, eax
call printf
# printf("Max: %ld (index %ld)\n", max, maxidx)
lea rdi, [rip + fmt_max]
mov rsi, r13
mov rdx, r14
xor eax, eax
call printf
jmp done
empty_vec:
lea rdi, [rip + msg_empty]
xor eax, eax
call printf
done:
xor eax, eax # return 0
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
.section .note.GNU-stack,"",@progbitsData. The format strings live in .rodata. fmt_in is "%ld" because each element is an 8-byte long. .bss reserves n (8 bytes) and vec (1000 × 8 = 8000 bytes). The loader zero-fills this section, and it takes no space in the executable. The .align 8 directive makes every element start at an address that is a multiple of 8.
Prologue and alignment. On entry to main, the caller's call has pushed a return address, so rsp is 8 mod 16. After push rbp it is 0 mod 16. The next four pushes add 32 bytes and keep that alignment, so every later call happens on an aligned stack with no sub rsp needed. These four registers are saved because the ABI requires main to return them unchanged.
Reading n. The format goes in rdi and the address of n in rsi, computed with a rip-relative lea. xor eax, eax tells the variadic function that no vector registers carry arguments. If scanf does not return 1 (empty input), or if n ≤ 0, control jumps to empty_vec. That path covers the "empty array" test, which prints Empty array.
The read_loop. This is a for loop that tests its condition first, with cmp r12, rbx and jge end_read. The key instruction is lea rsi, [rax + r12*8]. It computes &vec[i] without touching memory. The index lives in r12 and n in rbx, and both survive the repeated calls to scanf. A counter in rcx could be destroyed by any of those calls.
The scan_loop. The sum and the maximum start at vec[0], the index of the maximum at 0, and the loop index at 1. Each iteration loads vec[i] into rax, adds it to r12 and compares it with the maximum in r13. The signed jle no_max lets through only strictly greater values. In the example, 9 appears at indices 2 and 4, and the program reports index 2. Here rcx and rdx are safe to use because the loop contains no calls.
Output. Before the first printf, the index is copied from rdx to r14, because printf may overwrite rdx. The array base is no longer needed, so r14 is free. The two calls then pass their arguments in rdi, rsi and rdx, in ABI order. The epilogue restores the registers in reverse order and returns 0 in eax.
Test cases. The all-negative test (maximum −3, first at index 1) checks that the maximum was not initialised to 0. The single-element test runs zero iterations of scan_loop and must still print vec[0]. The large-values test gives a sum of 3999999999, which does not fit in a signed 32-bit integer. It confirms the use of 64-bit registers and %ld. The last case puts the maximum at index 4 to check that the final element is processed.
Test cases
| Case | Input | Expected output | Actual output | Result |
|---|---|---|---|---|
| example with repeated maximum | 6
4 -2 9 3 9 -5 | Sum: 18
Max: 9 (index 2) | Sum: 18
Max: 9 (index 2) | OK |
| empty array | 0 | Empty array | Empty array | OK |
| single negative element | 1
-7 | Sum: -7
Max: -7 (index 0) | Sum: -7
Max: -7 (index 0) | OK |
| all negative | 4
-8 -3 -3 -10 | Sum: -24
Max: -3 (index 1) | Sum: -24
Max: -3 (index 1) | OK |
| sum beyond 32 bits | 4
1000000000 1000000000 1000000000 999999999 | Sum: 3999999999
Max: 1000000000 (index 0) | Sum: 3999999999
Max: 1000000000 (index 0) | OK |
| maximum in last position | 5
0 0 0 0 1 | Sum: 1
Max: 1 (index 4) | Sum: 1
Max: 1 (index 4) | OK |
Actual outputs: code compiled with GNU as 2.44 (x86-64) and run in an isolated container on 28 October 2026.
Complexity
Time: Θ(n). read_loop makes exactly n calls to scanf. scan_loop runs n − 1 iterations. Each iteration executes a constant number of instructions: one load, one add, one compare and at most two movs. In practice the calls to scanf dominate the running time. Parsing text costs far more than the traversal.
Memory: a fixed 8 KB in .bss for the array, 8 bytes for n, and 48 bytes of stack (the return address plus five saved registers). This is O(1) in the input size, because the array is sized for the largest allowed n. A program that only needed the sum and maximum could skip storing the data altogether. The statement, however, requires storing it first.
Common mistakes
- Passing
scanfthe contents of the slot (mov rsi, [vec + i*8]) instead of its address (lea). The program writes to an arbitrary address and usually dies with a segmentation fault. - Using scale 4 or the
%dformat with an array of 8-byte elements. Values overlap or keep an uninitialised upper half, and sums of large values overflow 32 bits. - Keeping the read-loop counter in
rcxorrsi. These are caller-saved registers, andscanfis free to change them. The result is an infinite loop or skipped elements. - Comparing for the maximum with
ja/jbe(unsigned). Then −5 counts as greater than 9, because its two's-complement bit pattern is a huge unsigned number. - Initialising the maximum to 0. The program fails when every reading is negative, and the reported index is wrong too.
- Doing an odd number of pushes after
push rbpwithout compensating withsub rsp, 8. The stack is misaligned at the call, andprintforscanfmay crash on SSE instructions that require alignment.
Variants
Add the minimum and its index in the same pass
You need two more registers that the loop does not otherwise use, for example r8 for the minimum and r9 for its index. Initialise them to vec[0] and 0. Inside the loop, add cmp rax, r8 followed by jge no_min. Before the printf calls, copy both values into callee-saved registers, which means also saving r15. Five pushes after rbp misalign the stack, so add sub rsp, 8 to the prologue and the matching add rsp, 8 to the epilogue.
Array of 32-bit integers with a 64-bit sum
If the array holds ints (4 bytes), read with "%d" and address elements as [base + i*4]. The load must sign-extend to 64 bits, using movsxd rax, dword ptr [r14 + rcx*4], before adding to r12. A plain mov eax, [...] zero-extends instead and turns negative values into large positive ones. Keep %ld to print the sum.
On the syllabus
Walking an array through a base address and an index is the low-level form of what introductory courses teach as arrays and pointers. That unit appears in the syllabus of Algoritmos y Programación I (95.11) at the Universidad de Buenos Aires, which lists arrays, multidimensional arrays, the relationship between arrays and pointers, and pointer operators. Seeing in assembly that &vec[i] is nothing more than base + i*8 makes those ideas concrete. To revisit why scanf needs an address, see the solved exercise on pass by value and by reference in C.
Direct interaction with the system library and the operating system is covered by CC3301 Programación de Software de Sistemas at the Universidad de Chile, whose syllabus includes the Linux API for files and directories, processes with fork, exec and pipes, the shell, and signals. This exercise sits one level below that material. It deals with the calling convention that makes it possible to invoke those functions from machine code at all.
Article generated with AI.larebelion



Publicar un comentario