Lab 4: Nested Loops and Sub Routines
Table of Contents
Overview
Assembly uses goto statements to jump to a label, which is an instruction
at a specific memory location. As a result, loops do not need to be
nested in each other as they do in higher level languages. However,
you can put the inner loop in a function. Using the call
instruction
in ASM is similar to calling a function because it preserves the return
point. A jmp
statement does not.
Consider these two C programs that print a right triangle. They do the same thing. The first example does the code in nested loop but the second example moves the inner loop to a function. Where it does the work is not important.
C Nested Loop Examples
1#include <stdio.h>
2
3int main()
4{
5 int rows = 5;
6
7 for (int i = 1; i <= rows; i++)
8 {
9 // Perform work in the inner loop
10 for (int j = 1; j <= i; j++) {
11 printf("* ");
12 }
13
14 // Finish the work of the outer loop
15 printf("\n");
16 }
17 return 0;
18}
1#include <stdio.h>
2
3void print_row(int);
4
5int main()
6{
7 int rows = 5;
8
9 for (int i = 1; i <= rows; i++)
10 {
11 print_row(i); // Perform the work of the inner loop
12 printf("\n"); // Finish the work of the outer loop
13 }
14 return 0;
15}
16
17/**
18 * Performs the work of the inner loop
19 * int i: The counter of the outer loop
20 */
21void print_row(int i)
22{
23 for (int j = 1; j <= i; j++) {
24 printf("* ");
25 }
26}
ASM Nested Loop Examples
1segment .text
2asm_main: ; entry point (called from driver.c)
3 ; Initialize
4 enter 0,0 ; Initialize register EBP
5 pusha ; Push register contents to the stack
6 ; Start instructions
7 mov ecx, 5 ; ECX is i (this must be ECX for 'loop' instruction)
8
9outer:
10 mov eax, 0 ; reset j
11 inner_start:
12 cmp eax, ecx ; compare j and i
13 jge inner_end ; if (j => i) exit inner loop
14 inc eax ; j++
15 call print_int ; Print j
16 jmp inner_start ; continue inner loop
17 inner_end:
18 call print_nl
19 loop outer ; if ECX != 0, continue outer loop
20
21 ; Program ends when ECX reaches 0
22 ; End instructions, clean up
23 popa ; Restore register data from the stack
24 mov eax, 0 ; flush EAX of data
25 leave ; restore the stack
26 ret ; Return from main back into C library wrapper
We can replicate the functionality by moving the inner loop to another location in the code. However, the logic becomes fragment. We have to put in a label inside of the outer loop so that the inner loop knows where to go after finishing its work.
1segment .text
2asm_main: ; entry point (called from driver.c)
3 ; Initialize
4 enter 0,0 ; Initialize register EBP
5 pusha ; Push register contents to the stack
6 ; Start instructions
7 mov ecx, 5 ; ECX is i (this must be ECX for 'loop' instruction)
8
9; start outer loop
10outer_start:
11 mov eax, 0 ; reset j
12 jmp inner_start ; Perform the work of the inner loop
13
14; Do the work of the inner loop
15outer_work: ; <--- The inner loop needs this reference
16 call print_nl
17 loop outer_start ; if ECX != 0, continue outer loop
18 jmp end ; The work of the loop is done. Jump to end.
19
20; do the work of the inner loop
21inner_start:
22 cmp eax, ecx ; compare j and i
23 jge outer_work ; if (j => i) return to outer loop
24 inc eax ; j++
25 call print_int ; Print j
26 jmp inner_start ; continue inner loop
27
28; All done
29end:
30 ; End instructions, clean up
31 popa ; Restore register data from the stack
32 mov eax, 0 ; flush EAX of data
33 leave ; restore the stack
34 ret ; Return from main back into C library wrapper
NASM Subroutines
A subroutine (function or method) is used to perform repeat actions to
avoid code duplication and to simplify program logic. We use subroutines
print_int
to print an integer to the console. Otherwise, we would
need to insert this code block every time we want to print a value:
1push eax
2push dword int_format
3call _printf
4pop ecx
5pop ecx
Here is the template for a subroutine:
1label:
2 push ecx ; save register X data on stack
3 ; do something with ECX (or other register)
4 pop ecx ; restore data to register X
5 ret ; return to the address that called the label
1segment .text
2asm_main: ; entry point (called from driver.c)
3 ; Initialize
4 enter 0,0 ; Initialize register EBP
5 pusha ; Push register contents to the stack
6 ; Start instructions
7 mov ecx, 5 ; ECX is i (this must be ECX for 'loop' instruction)
8
9; start outer loop
10outer_start:
11 mov eax, 0 ; reset j
12 call inner_start ; Perform the work of the inner loop
13 call print_nl
14 loop outer_start ; if ECX != 0, continue outer loop
15 jmp end ; The work of the loop is done. Jump to end.
16
17; do the work of the inner loop
18inner_start:
19 cmp eax, ecx ; compare j and i
20 jge return ; if (j => i) call "ret"
21 inc eax ; j++
22 call print_int ; Print j
23 jmp inner_start ; continue inner loop
24return:
25 ret ; return to the last address called (line 12)
26
27end:
28
29 ; End instructions, clean up
30 popa ; Restore register data from the stack
31 mov eax, 0 ; flush EAX of data
32 leave ; restore the stack
33 ret ; Return from main back into C library wrapper
Making (inverse) Right Triangles
We all like to create useless programs. :))
Use NASM to create a triangle or similar design using a character,
such as *
. You will use a nested for
loop. The width should
not exceed 150 characters to prevent wrapping.
Here is the C Code to help you.
Let’s make things more complicated. You can’t call print_string
more
than once per loop iteration. It would be too easy to call print_string
twice. Instead, use EDX
to determine when to exit the inner loop.
Use these variables:
Development Tips
The simplest implementation is to use the loop
command to print
an inverse triangle (start at the maximum and then count down). See
one of the ASM Nested Loop Examples listed above. You can use these
registers.
EAX: ; temp var, the character to print EBX: ; column counter (inner loop) ECX: ; row counter (outer loop) EDX: ; temp var, loop control variable
Printing the triangle with the base at the bottom is more complicated. You do not have enough registers to store the data. So, you have to push the data in one of the registers to the stack and then restore it.
EAX: ; column loop counter; the character to print EBX: ; row loop control variable ECX: ; row loop counter EDX: ; column loop control variable
The column loop would look something like this:
1column: 2 ; compare j and i 3 ; if (j => i), jump out of loop 4 inc eax ; j++ 5 push eax ; save counter 6 mov eax, char ; get character to print 7 ; Print char 8 pop eax ; restore counter 9 jmp column ; continue column loop
Task 1: Symmetrical Triangle
Create a symmetrical triangle, such as 3x3 or 5x5. EDX == ECX
Tip
Add a space between each *
if you want the height to match the width.
1*
2* *
3* * *
4* * * *
5* * * * *
See the Lab 4.1 Solutions if you need help.
Task 2: Fat Triangle
Modify the loop variables to create more columns than rows. The slope
of the triangle should consistent. EDX == (ECX * 2)
or
EDX == (ECX * 3)
are examples.
5 * * * * * * * * * *
4 * * * * * * * *
3 * * * * * *
2 * * * *
1 * *
0 *
See the Lab 4.2 Solutions if you need help.
Task 3: Curves
Create a regular or inverse curve from squaring numbers (n*n).
For a challenge, create power table (2 n). The power curve is
much harder because either (1) you have to keep track of the last result
by storing it using a RESX
label to square or (2), you can create
a subroutine.
Note
Recall that mul ecx
uses both EAX and EDX.
1*
2**
3*****
4**********
5*****************
6**************************
7*************************************
8**************************************************
9*****************************************************************
1*
2**
3****
4********
5****************
6********************************
7****************************************************************
See the Lab 4.3 Solutions if you need help.
Task 4: Skinny Triangle
Try a more challenging triangle by modify the loop variables to
create more rows than columns using the div
command.
; EAX = EAX/ECX; columns = rows/x
mov eax, ecx ; move the current row value into EAX
push ecx ; save current row value on the stack
mov ecx, 5 ; put our divisor in ECX
mov edx, 0 ; initialize EDX (where the remainder goes)
div ecx ; EAX = EAX / ECX
mov edx, eax ; put the number of columns to print in EDX
pop ecx ; restore the row value from the stack to ECX
1*
2*
3*
4*
5**
6**
7**
8**
9**
10***
11***
12***
13***
14***
15****
16****
17****
18****
19****
20*****
21*****
See the Lab 4.4 Solutions if you need help.