Chapter 10
Program Flow Constructs

The assembly language we have studied thus far is executed in sequence. In this chapter we will learn how to organize assembly language instructions to implement the other two required program flow constructs — repetition and binary decision.

Text string manipulations provide many examples of using program flow constructs, so we will use them to illustrate many of the concepts. Almost any program displays many text string messages on the screen, which are simply arrays of characters.

10.1 Repetition

The algorithms we choose when programming interact closely with the data storage structure. As you probably know, a string of characters is stored in an array. Each element of the array is of type char, and in C the end of the data is signified with a sentinel value, the NUL character (see Table 2.3 on page 49).

The other technique for specifying the length of the string is to store the number of characters in the string together with the string. This is implemented in Pascal by storing the number of characters in the first byte of the array, and the actual characters are stored immediately following.

Array processing is usually a repetitive task. The processing of a character string is a good example of repetition. Consider the C program in Listing 10.1.

 
1/* 
2 * helloWorld1.c 
3 * "hello world" program using the write() system call 
4 * one character at a time. 
5 * Bob Plantz - 12 June 2009 
6 */ 
7#include <unistd.h> 
8 
9int main(void) 
10{ 
11    char *aString = "Hello World.\n"; 
12 
13    while (*aString != \0) 
14    { 
15        write(STDOUT_FILENO, aString, 1); 
16        aString++; 
17    } 
18 
19    return 0; 
20}
Listing 10.1: Displaying a string one character at a time (C).

The while statement on lines 13 – 17,

      while (*aString != \0) 
      { 
           ... 
      }

controls the execution of the statements within the {…} block.

  1. It evaluates the boolean expression *aString != ’\0’.
  2. If the boolean expression evaluates to false, program flow jumps to the statement immediately following the {…} block.
  3. If the boolean expression evaluates to true, program flow enters the {…} block and executes the statements there in sequence.
  4. At the end of the {…} block program flow jumps back up to the evaluation of the boolean expression.

The pointer variable is incremented with the

      aString++;

statement. Notice that this variable must be changed inside the {…} block. Otherwise, the boolean expression will always evaluate to true, giving an “infinite” loop.

It is important that you identify the variable that the while construct uses to control program flow — the Loop Control Variable (LCV). Make sure that the value of the LCV is changed within the {…} block. Note that there may be more than one LCV.

The way that the while construct controls program flow can be seen in the flow chart in Figure 10.1.


PIC


Figure 10.1: Flow chart of a while loop. The large diamond represents a binary decision that leads to two possible paths, “true” or “false.” Notice the path that leads back to the top of the while loop after the body has been executed.


This flow chart shows that we need the following assembly language tools to construct a while loop:

We will explore instructions that provide these tools in the next three subsections.

10.1.1 Comparison Instructions

Most arithmetic and logic instructions affect the condition code bits in the rflags register. (See page 470.) In this section we will look at two instructions that are used to set the condition codes to show the relationship between two values without changing either of them.

One is cmp (compare). The syntax is

cmps source, destination

where s denotes the size of the operand:

s meaning number of bits
b byte 8
w word 16
l longword 32
q quadword 64

Intel® Syntax

cmp destination, source

The cmp operation consists of subtracting the source operand from the destination operand and setting the condition code bits in the rflags register accordingly. Neither of the operand values is changed. The subtraction is done internally simply to get the result and set the OF, SF, ZF, AF, PF, CF condition codes according to the result.

The other instruction is test. The syntax is

tests source, destination

where s denotes the size of the operand:

s meaning number of bits
b byte 8
w word 16
l longword 32
q quadword 64

Intel® Syntax

test destination, source

The test operation consists of performing a bit-wise and between the two operands and setting the condition codes in the rflags register accordingly. Neither of the operand values is changed. The and operation is done internally simply to get the result and set the SF, ZF, and PF condition codes according to the result. The OF and CF are set to 0, and the AF value is undefined.

10.1.2 Conditional Jumps

These instructions are used to alter the flow of the program depending on the settings of the condition code bits in the rflags register. The general format is

jcc label

where cc is a 1 – 4 letter sequence specifying the condition codes, and label is a memory location. Program flow is transferred to label if cc is true. Otherwise, the instruction immediately following the conditional jump is executed. The conditional jump instructions are listed in Table 10.1.


instruction action condition codes
ja jump if above (CF = 0) (ZF = 0)
jae jump if above or equal CF = 0
jb jump if below CF = 1
jbe jump if below or equal (CF = 1) + (ZF = 1)
jc jump if carry CF = 1
jcxz jump if cx register zero
jecxz jump if ecx register zero
jrcxz jump if rcx register zero
je jump if equal ZF = 1
jg jump if greater (ZF = 0) (SF = OF)
jge jump if greater or equal SF = OF
jl jump if less SFOF
jle jump if less or equal (ZF = 1) + (SFOF)
jna jump if not above (CF = 1) + (ZF = 1)
jnae jump if not above or equal CF = 1
jnb jump if not below CF = 0
jnbe jump if not below or equal (CF = 0) (ZF = 0)
jnc jump if not carry CF = 0
jne jump if not equal ZF = 0
jng jump if not greater (ZF = 1) + (SFOF)
jnge jump if not greater or equal SFOF
jnl jump if not less SF = OF
jnle jump if not less or equal (ZF = 0) (SF = OF)
jno jump if not overflow OF = 0
jnp jump if not parity or equal PF = 0
jns jump if not sign SF = 0
jnz jump if not zero ZF = 0
jo jump if overflow OF = 1
jp jump if parity PF = 1
jpe jump if parity even PF = 1
jpo jump if parity odd PF = 0
js jump if sign SF = 1
jz jump if zero ZF = 1

Table 10.1: Conditional jump instructions.

A good way to appreciate the meaning of the cc sequences in this table is to consider a very common application of a conditional jump:

        cmpb   %al, %bl 
        jae    somePlace 
        movb   $0x123, %ah

If the value in the bl register is numerically above the value in the al register, or if they are equal, then program control transfers to the address labeled “somePlace.” Otherwise, program control continues with the movb instruction.

The differences between “greater” versus “above”, and “less” versus “below”, are a little subtle. “Above” and “below” refer to a sequence of unsigned numbers. For example, characters would probably be considered to be unsigned in most applications. “Greater” and “less” refer to signed values. Integers are commonly considered to be signed.

Table 10.2 lists four conditional jumps that are commonly used when processing unsigned values, and Table 10.3 lists four commonly used with signed values.


instruction

meaning

immediately after a cmp




ja

jump above

jump if destination is above source in sequence




jae

jump above or equal

jump if destination is above or in same place as source in sequence




jb

jump below

jump if destination is below source in sequence




jbe

jump below or equal

jump if destination is below or in same place as source in sequence





Table 10.2: Conditional jump instructions for unsigned values.


instruction

meaning

immediately after a cmp




jg

jump greater

jump if destination is greater than source




jge

jump greater or equal

jump if destination is greater than or equal to source




jl

jump less

jump if destination is less than source




jle

jump less or equal

jump if destination is less than or equal to source





Table 10.3: Conditional jump instructions for signed values.

Since most instructions affect the settings of the condition codes in the rflags register, each must be used immediately after the instruction that determines the conditions that the programmer intends to cause the jump.

HINT: It is easy to forget how the order of the source and destination controls the conditional jump in this construct. Here is a place where the debugger can save you time. Simply put a breakpoint at the conditional jump instruction. When the program stops there, look at the values in the source and destination. Then use the si debugger command to execute one instruction and see where it goes.

The jump instructions bring up another addressing mode — rip-relative. 1

rip-relative:
The target is a memory address determined by adding an offset to the current address in the rip register.
syntax: a programmer-defined label example: je  somePlace

The offset, which can be positive or negative, is stored immediately following the opcode for the instruction in two’s complement format. Thus, the offset becomes a part of the instruction, similar to the immediate data addressing mode. Just like the immediate addressing mode, the offset is stored in little endian order in memory.

The following steps occur during program execution of a jcc instruction (recall Figure 6.5):

  1. The jump instruction, including the offset value, is fetched.
  2. As always, the rip register is incremented by the number of bytes in the jump instruction, including the offset value that is stored as part of the jump instruction.
  3. If the conditions to cause a jump are true, the offset is added to the rip register.
  4. If they are not true, the instruction has no effect.

When a conditional jump instruction is assembled, the assembler computes the number of bytes from the jump instruction to the specified label. The assembler then subtracts the number of bytes in the jump instruction from the distance to the label to yield the offset. This computed offset is stored as part of the jump instruction. Each jump instruction has several forms, depending on the number of bytes that must be used to store the offset. Note that the offset is stored in two’s complement format to allow for negative jumps.

For example, if the offset will fit into eight bits the opcode for the je instruction is 7416, and it is 0f8416 if more than eight bits are required to store the offset (in which case the offset is stored in as a thirty-two bit value). The machine code is shown in Table 10.4 for four different target address offsets. Notice that the 32-bit offsets are stored in little endian order in memory.


distance to target address

machine code

(bytes, decimal)

(hexadecimal)



+100

7462



-100

749a



+300

0f8426010000



-300

0f84cefeffff




Table 10.4: Machine code for the je instruction. Four different distances to the jump target address. Notice that the 32-bit offsets are stored in little endian order.

10.1.3 Unconditional Jump

We also need an instruction that unconditionally transfers control to another location in the program. The instruction has three forms:

jmp label

jmp *register

jmp *memory

Program flow is transferred to the location specified by the operand.

The first form is limited to those situations where the distance, in number of bytes, to the target location will fit within a 32-bit signed integer. The addressing mode is rip-relative. That is, the 32-bit signed integer is added to the current value in the rip register. This is sufficient for most cases.

In the other two forms, the target address is stored in the specified register or memory location, and the operand is accessed indirectly. The address is an unsigned 64-bit value. The jmp instruction moves this stored address directly into the rip register, replacing the address that was in there. The “*” character is used to indicate “indirection.”

BE CAREFUL: The unconditional jump uses “*” for indirection, while all other instructions use (register).” It might be tempting to use something like “*(%rax).” Although the () are not an error here, they are superfluous. They have essentially the same effect as something like (x) in an algebraic expression.

The three ways to use an unconditional jump are shown in Listing 10.2.

 
1# jumps.s 
2# demonstrates unconditional jumps 
3# Bob Plantz - 12 June 2009 
4# global variable 
5        .data 
6pointer: 
7        .quad   0 
8format: 
9        .string "The jump pattern is %x.\n" 
10# code 
11        .text 
12        .globl  main 
13        .type   main, @function 
14main: 
15        pushq   %rbp        # save frame pointer 
16        movq    %rsp, %rbp  # set new frame pointer 
17 
18        movl    $7, %esi    # assume all three jumps 
19        jmp     here1 
20        andl    $0xfffffffe, %esi # no jump, turn off bit 0 
21here1: 
22        leaq    here2, %rax 
23        jmp     *%rax 
24        andl    $0xfffffffd, %esi # no jump, turn off bit 1 
25here2: 
26        leaq    here3, %rax 
27        movq    %rax, pointer 
28        jmp     *pointer 
29        andl    $0xfffffffb, %esi # no jump, turn off bit 2 
30here3: 
31        movl    $format, %edi 
32        movl    $0, %eax    # no floats 
33        call    printf      # show pattern 
34 
35        movl    $0, %eax    # return 0; 
36        movq    %rbp, %rsp  # restore stack pointer 
37        popq    %rbp        # restore frame pointer 
38        ret
Listing 10.2: Unconditional jumps.

The most commonly used form is rip-relative as shown on line 19:

19        jmp     here1

On lines 22 – 23 an address is loaded into a register, then the jump is made indirectly via the register to that address.

22        leaq    here2, %rax 
23        jmp     *%rax

Lines 26 – 28 show how an address can be stored in memory, then the memory used indirectly for the jump.

26        leaq    here3, %rax 
27        movq    %rax, pointer 
28        jmp     *pointer

Of course, the indirect techniques are not required in this simple example, but they might be needed for some programs.

10.1.4 while Loop

We are now prepared to look at how a while loop is constructed at the assembly language level. As usual, we begin with the assembly language generated by the gcc compiler for the program in Listing 10.1, which is shown in Listing 10.3 with comments added.

 
1        .file  "helloWorld1.c" 
2        .section      .rodata 
3.LC0: 
4        .string"Hello World.\n" 
5        .text 
6        .globlmain 
7        .type  main, @function 
8main: 
9        pushq  %rbp 
10        movq  %rsp, %rbp 
11        subq  $16, %rsp 
12        movq  $.LC0, -8(%rbp) # pointer to string 
13        jmp    .L2             # go to bottom of loop 
14.L3: 
15        movq  -8(%rbp), %rax  # load pointer to string 
16        movl  $1, %edx        # 3rd arg. - 1 character 
17        movq  %rax, %rsi      # 2nd arg. - pointer 
18        movl  $1, %edi        # 1st arg. - standard out 
19        call  write 
20        addq  $1, -8(%rbp)    # aString++; 
21.L2: 
22        movq  -8(%rbp), %rax  # load pointer 
23        movzbl(%rax), %eax    # get current character 
24        testb  %al, %al        # is it NUL? 
25        jne    .L3             # no, go to top of loop 
26        movl  $0, %eax 
27        leave 
28        ret 
29        .size  main, .-main 
30        .ident"GCC: (Ubuntu/Linaro 4.7.0-7ubuntu3) 4.7.0" 
31        .section      .note.GNU-stack,"",@progbits
Listing 10.3: Displaying a string one character at a time (gcc assembly language). Comments added.

Let us consider the loop:

12        movq  $.LC0, -8(%rbp) # pointer to string 
13        jmp    .L2             # go to bottom of loop 
14.L3: 
15        movq  -8(%rbp), %rax  # load pointer to string 
16        movl  $1, %edx        # 3rd arg. - 1 character 
17        movq  %rax, %rsi      # 2nd arg. - pointer 
18        movl  $1, %edi        # 1st arg. - standard out 
19        call  write 
20        addq  $1, -8(%rbp)    # aString++; 
21.L2: 
22        movq  -8(%rbp), %rax  # load pointer 
23        movzbl(%rax), %eax    # get current character 
24        testb  %al, %al        # is it NUL? 
25        jne    .L3             # no, go to top of loop

Notice that after initializing the loop control variable it jumps to the condition test,

12        movq  $.LC0, -8(%rbp) # pointer to string 
13        jmp    .L2             # go to bottom of loop

which is at the bottom of the loop:

21.L2: 
22.L2: 
23        movq  -8(%rbp), %rax  # load pointer 
24        movzbl(%rax), %eax    # get current character 
25        testb  %al, %al        # is it NUL? 
26        jne    .L3             # no, go to top of loop

Let us rearrange the instructions so that this is a true while loop — the condition test is at the top of the loop. The exit condition has been changed from jne to je for correctness. The original is on the left, the rearranged on the right:

Compiler’s version

12        movq  $.LC0, -8(%rbp) 
13        jmp    .L2 
14.L3: 
15        movq  -8(%rbp), %rax 
16        movl  $1, %edx 
17        movq  %rax, %rsi 
18        movl  $1, %edi 
19        call  write 
20        addq  $1, -8(%rbp) 
21.L2: 
22        movq  -8(%rbp), %rax 
23        movzbl(%rax), %eax 
24        testb  %al, %al 
25        jne    .L3

Test at top of loop

12        movq  $.LC0, -8(%rbp) 
13.L2: 
14        movq  -8(%rbp), %rax 
15        movzbl(%rax), %eax 
16        testb  %al, %al 
17        je    .L3 
18        movq  -8(%rbp), %rax 
19        movl  $1, %edx 
20        movq  %rax, %rsi 
21        movl  $1, %edi 
22        call  write 
23        addq  $1, -8(%rbp) 
24        jmp    .L2 
25.L3:
Both versions have exactly the same number of instructions. However, the unconditional jump instruction, jmp, is executed every time through the loop when testing at the top but is executed only once in the compiler’s version. Thus, the compiler’s version is more efficient. The savings is probably insignificant in the vast majority of applications. However, if a loop is nested within another loop or two, the difference could be important.

We also see another version of the mov instruction on line 22 of the compiler’s version:

22        movzbl  (%rax), %eax

This instruction converts the data size from 8-bit to 32-bit, placing zeros in the high-order 24 bits, as it copies the byte from memory to the eax register. The memory address of the copied byte is in the rax register. (Yes, this instruction writes over the address in the register as it executes.)

The x86-64 architecture includes instructions for extending the size of a value by adding more bits to the left. There are two ways to do this:

Sign extension can be accomplished with the movs instruction:

movssd source, destination

where s denotes the size of the source operand and d the size of the destination operand. (Use the s column for d.)

s meaning number of bits
b byte 8
w word 16
l longword 32
q quadword 64

It can be used to move an 8-bit value from memory or a register into a 16-, 32-, or 64-bit register; move a 16-bit value from memory or a register into a 32-bit register; or move a 32-bit value from memory or a register into a 64-bit register. The “s” causes the rest of the high-order bits in the destination register to be a copy of the sign bit in the source value. It does not affect the condition codes in the rflags register.

In the Intel syntax the instruction is movsx. The size of the data is determined by the operands, so the size characters (b, w, l, or q) are not appended to the instruction, and the order of the operands is reversed.

Intel® Syntax

movsx destination, source

In some cases the Intel syntax is ambiguous. Intel-syntax assemblers use keywords to specify the data size in such cases. For example, the nasm assembler uses
    movsx destination, BYTE [source]
to move one byte and zero extend, and uses
    movsx destination, WORD [source]
to move two bytes and sign extend.

Zero extension can be accomplished with the movz instruction:

movzsd source, destination

where s denotes the size of the source operand and d the size of the destination operand. (Use the s column for d.)

s meaning number of bits
b byte 8
w word 16
l longword 32
q quadword 64

It can be used to move an 8-bit value from memory or a register into a 16-, 32-, or 64-bit register; or move a 16-bit value from memory or a register into a 32-bit register. The “z” causes the rest of the high-order bits in the destination register to be set to zero. It does not affect the condition codes in the rflags register. Recall that moving a 32-bit value from memory or a register into a 64-bit register sets the high-order 32 bits to zero, so there is no movzlq instruction.

In the Intel syntax the instruction is movzx The size of the data is determined by the operands, so the size characters (b, w, l, or q) are not appended to the instruction, and the order of the operands is reversed.

Intel® Syntax

movzx destination, source

There is also a set of instructions that double the size of data in portions of the rax register, shown in Table 10.5. The doubling operation includes sign extention into the affected higher-order portion of the register.


AT&T syntax Intel® syntax start result




cbtw cbw byte in al word in ax
cwtl cwde word in ax long in eax
cltq cdqe lonq in eax quad in rax

Table 10.5: Instructions to double data size. These instructions do not specify any operands, but they may change the rax register.

Notice that these instructions do not explicitly specify any operands, but they may change the rax register. They do not affect the condition codes in the rflags register.

Returning to while loops, the general structure of a count-controlled while loop is shown in Listing 10.4.

 
1# generalWhile.s 
2# general structure of a while loop (not a program) 
3# 
4#    count = 10; 
5#    while (count > 0) 
6#    { 
7#        // loop body 
8#        count--; 
9#    } 
10# 
11# Bob Plantz - 10 June 2009 
12 
13        movl    $10, count(%rbp) # initialize loop control variable 
14whileLoop: 
15        cmpb    $0, count(%rbp)  # check continuation conditions 
16        jle     whileDone        # if false, leave loop 
17   # ------ 
18   # loop body processing 
19   # ------ 
20        subl    $1, count(%rbp)  # change loop control variable 
21        jmp     whileLoop        # back to top 
22whileDone: 
23   # next programming construct
Listing 10.4: General structure of a count-controlled while loop.

This is not a complete program or even a function. It simply shows the key elements of a while loop.

Loops, of course, take the most execution time in a program. However, in almost all cases code readability is more important than efficiency. You should determine that a loop is an efficiency bottleneck before sacrificing its structure for efficiency. And then you should generously comment what you have done.

Our assembly language version of a “Hello world” program in Listing 10.5 uses a sentinel-controlled while loop.

 
1# helloWorld3.s 
2# "hello world" program using the write() system call 
3# one character at a time. 
4# Bob Plantz - 12 June 2009 
5 
6# Useful constants 
7        .equ    STDOUT,1 
8# Stack frame 
9        .equ    aString,-8 
10        .equ    localSize,-16 
11# Read only data 
12        .section  .rodata 
13theString: 
14        .string "Hello world.\n" 
15# Code 
16        .text 
17        .globl  main 
18        .type   main, @function 
19main: 
20        pushq   %rbp        # save base pointer 
21        movq    %rsp, %rbp  # set new base pointer 
22        addq    $localSize, %rsp  # for local var. 
23 
24        movl    $theString, %esi 
25        movl    %esi, aString(%rbp) # *aString = "Hello World.\n"; 
26whileLoop: 
27        movl    aString(%rbp), %esi # current char in string 
28        cmpb    $0, (%esi)  # null character? 
29        je      allDone     # yes, all done 
30 
31        movl    $1, %edx    # one character 
32        movl    $STDOUT, %edi  # standard out 
33        call    write       # invoke write function 
34 
35        incl    aString(%rbp)  # aString++; 
36        jmp     whileLoop   # back to top 
37allDone: 
38        movl    $0, %eax    # return 0; 
39        movq    %rbp, %rsp  # restore stack pointer 
40        popq    %rbp        # restore base pointer 
41        ret
Listing 10.5: Displaying a string one character at a time (programmer assembly language).

Consider the sequence on lines 26 – 28:

26whileLoop: 
27        movl    aString(%rbp), %esi # current char in string 
28        cmpb    $0, (%esi)  # null character?

We had to move the pointer value into a register in order to dereference the pointer. These two instructions implement the C expression:

   (*aString != \0)

In particular, you have to move the address into a register, then dereference it with the “(register)” syntax.

Be careful not to confuse this with the indirection operator, “*”, used with the jmp instruction that you saw in Section 10.1.3, especially since the assembly language indirection operator is the same as the dereference operator in C/C++.

There are two common errors when using the assembly language syntax.

BE CAREFUL: The C/C++ syntax for the NUL character, ’0’, is not recognized by the gnu assembler, as. From Table 2.3 we see that the bit pattern for the NUL character is 0x00, and this value must be used in the gnu assembly language.

We also need to add one to the pointer variable so as to move it to the next character in the string. Adding one is a common operation, so there is an operator that simply adds one,

incs source

where s denotes the size of the operand:

s meaning number of bits
b byte 8
w word 16
l longword 32
q quadword 64

The inc instruction adds one to the source operand. The operand can be a register or a memory location.

On line 35 of the program in Listing 10.5, incl is used to add one to the address stored in memory:

35        incl    aString(%rbp)  # aString++;
BE CAREFUL: It is easy to think that the instruction ought to be incb since each character is only one byte. The address in this program is 32 bits, so we have to use incl. And, of course, when we use a 64-bit address, we need to use incq. Don’t forget that the value we are adding one to is an address, not the value stored at that address.

Subtracting one from a counter is also a common operation. The dec instruction subtracts one from an operand and sets the rflags register accordingly. The operand can be a register or a memory location.

decs source

where s denotes the size of the operand:

s meaning number of bits
b byte 8
w word 16
l longword 32
q quadword 64

A decl instruction is used on line 27 in Listing 10.6 to both subtract one from the counter variable and to set the condition codes in the rflags register for the jg instruction.

 
1# printStars.s 
2# prints 10 * characters on a line 
3# Bob Plantz - 12 June 2009 
4 
5# Useful constants 
6        .equ    STDOUT,1 
7# Stack frame 
8        .equ    theChar,-1 
9        .equ    counter,-16 
10        .equ    localSize,-16 
11# Code 
12        .text 
13        .globl  main 
14        .type   main, @function 
15main: 
16        pushq   %rbp        # save base pointer 
17        movq    %rsp, %rbp  # set new base pointer 
18        addq    $localSize, %rsp  # for local var. 
19 
20        movb    $*, theChar(%rbp)  # character to print 
21        movl    $10, counter(%rbp)   # ten times 
22doWhileLoop: 
23        leaq    theChar(%rbp), %rsi  # address of char 
24        movl    $1, %edx    # one character 
25        movl    $STDOUT, %edi  # standard out 
26        call    write       # invoke write function 
27        decl    counter(%rbp)  # counter--; 
28        jg      doWhileLoop # repeat if > 0 
29 
30        movl    $0, %eax    # return 0; 
31        movq    %rbp, %rsp  # restore stack pointer 
32        popq    %rbp        # restore base pointer 
33        ret
Listing 10.6: A do-while loop to print 10 characters.

This is clearly better than using

        .... 
        subl    $1, counter(%rbp)  # counter--; 
        cmpl    $0, counter(%rbp) 
        jg      doWhileLoop        # repeat if > 0 
        ....

This program also demonstrates how to implement a do-while loop.

10.2 Binary Decisions

We now know how to implement two of the primary program flow constructs — sequence and repetition. We continue on with the third — binary decision. You know this construct from C/C++ as the if-else.

We start the discussion with a common example — a simple program that asks the user whether changes should be saved or not (Listing 10.7). This example program does not do anything, so there really is nothing to change, but you have certainly seen this construct. (As usual, this program is meant to illustrate concepts, not good C/C++ programming practices.)

 
1/* 
2 * yesNo1.c 
3 * Prompts user to enter a y/n response. 
4 * 
5 * Bob Plantz - 12 June 2009 
6 */ 
7 
8#include <unistd.h> 
9 
10int main(void) 
11{ 
12    char *ptr; 
13    char response; 
14 
15    ptr = "Save changes? "; 
16 
17    while (*ptr != \0) 
18    { 
19        write(STDOUT_FILENO, ptr, 1); 
20        ptr++; 
21    } 
22 
23    read (STDIN_FILENO, &response, 1); 
24 
25    if (response == y) 
26    { 
27        ptr = "Changes saved.\n"; 
28        while (*ptr != \0) 
29        { 
30            write(STDOUT_FILENO, ptr, 1); 
31            ptr++; 
32        } 
33    } 
34    else 
35    { 
36        ptr = "Changes discarded.\n"; 
37        while (*ptr != \0) 
38        { 
39            write(STDOUT_FILENO, ptr, 1); 
40            ptr++; 
41        } 
42    } 
43    return 0; 
44}
Listing 10.7: Get yes/no response from user (C).

Let’s look at the flow of the program that the if-else controls.

  1. The boolean expression (response == ’y’) is evaluated.
  2. If the evaluation is true, the first block, the one that displays “Changes saved.”, is executed.
  3. If the evaluation is false, the second block, the one that displays “Changes discarded.”, is executed.
  4. In both cases the next statement to be executed is the return 0;

The program control flow of the if-else construct is illustrated in Figure 10.2.


PIC


Figure 10.2: Flow chart of if-else construct. The large diamond represents a binary decision that leads to two possible paths, “true” or “false.” Notice that either the “then” block or the “else” block is executed, but not both. Each leads to the end of the if-else construct.


We already know all the assembly language instructions needed to implement the if-else in Listing 10.7. The important thing to note is that there must be an unconditional jump at the end of the “then” block to transfer program flow around the “else” block. The assembly language generated for this program is shown in Listing 10.8.

 
1        .file  "yesNo1.c" 
2        .section      .rodata 
3.LC0: 
4        .string"Save changes? " 
5.LC1: 
6        .string"Changes saved.\n" 
7.LC2: 
8        .string"Changes discarded.\n" 
9        .text 
10        .globlmain 
11        .type  main, @function 
12main: 
13        pushq  %rbp 
14        movq  %rsp, %rbp 
15        subq  $16, %rsp 
16        movq  $.LC0, -8(%rbp) 
17        jmp    .L2 
18.L3: 
19        movq  -8(%rbp), %rax 
20        movl  $1, %edx 
21        movq  %rax, %rsi 
22        movl  $1, %edi 
23        call  write 
24        addq  $1, -8(%rbp) 
25.L2: 
26        movq  -8(%rbp), %rax 
27        movzbl(%rax), %eax 
28        testb  %al, %al 
29        jne    .L3 
30        leaq  -9(%rbp), %rax   # place to store user response 
31        movl  $1, %edx 
32        movq  %rax, %rsi 
33        movl  $0, %edi 
34        call  read 
35        movzbl-9(%rbp), %eax   # get user response 
36        cmpb  $121, %al        # response == y ? 
37        jne    .L4              # no, go to else part 
38        movq  $.LC1, -8(%rbp)  # yes, write "Changes saved.\n" 
39        jmp    .L5 
40.L6: 
41        movq  -8(%rbp), %rax 
42        movl  $1, %edx 
43        movq  %rax, %rsi 
44        movl  $1, %edi 
45        call  write 
46        addq  $1, -8(%rbp) 
47.L5: 
48        movq  -8(%rbp), %rax 
49        movzbl(%rax), %eax 
50        testb  %al, %al 
51        jne    .L6 
52        jmp    .L7              # jump around else part 
53.L4:                             # else part, 
54        movq  $.LC2, -8(%rbp)  #   write "Changes discarded.\n" 
55        jmp    .L8 
56.L9: 
57        movq  -8(%rbp), %rax 
58        movl  $1, %edx 
59        movq  %rax, %rsi 
60        movl  $1, %edi 
61        call  write 
62        addq  $1, -8(%rbp) 
63.L8: 
64        movq  -8(%rbp), %rax 
65        movzbl(%rax), %eax 
66        testb  %al, %al 
67        jne    .L9 
68.L7:                             # after if-else statement 
69        movl  $0, %eax 
70        leave 
71        ret 
72        .size  main, .-main 
73        .ident"GCC: (Ubuntu/Linaro 4.7.0-7ubuntu3) 4.7.0" 
74        .section      .note.GNU-stack,"",@progbits
Listing 10.8: Get yes/no response from user (gcc assembly language).

The general structure of an if-else construct is shown in Listing 10.9.

 
1# generalIf-else.s 
2# general structure of an if-else (not a program) 
3# 
4#    if (response == y’) 
5#    { 
6#        then part 
7#    } 
8#    else 
9#    { 
10#        else part 
11#    } 
12# 
13# Bob Plantz - 10 June 2009 
14 
15        cmpb    $y, response(%rbp) # check conditions 
16        jne     noChange        # false, go to else part 
17   # ------ 
18   # "then" part processing 
19   # ------ 
20        jmp     allDone         # go to end of if-else 
21noChange: 
22   # ------ 
23   # "else" part processing 
24   # ------ 
25allDone: 
26   # next programming construct
Listing 10.9: General structure of an if-else construct. Don’t forget the “jmp” at the end of the “then” block (line 20).

This is not a complete program or even a function. It simply shows the key elements of an if-else construct.

Our assembly language version of the yes/no program in Listing 10.10 follows this general pattern. It, of course, uses more meaningful labels than what the compiler generated.

 
1# yesNo2.s 
2# Prompts user to enter a y/n response. 
3# Bob Plantz - 12 June 2009 
4 
5# Useful constants 
6        .equ    STDIN,0 
7        .equ    STDOUT,1 
8# Stack frame 
9        .equ    response,-1 
10        .equ    ptr,-16 
11        .equ    localSize,-16 
12# Read only data 
13        .section  .rodata 
14queryMsg: 
15        .string "Save changes? " 
16saveMsg: 
17        .string "Changes saved.\n" 
18discardMsg: 
19        .string "Changes discarded.\n" 
20# Code 
21        .text 
22        .globl  main 
23        .type   main, @function 
24main: 
25        pushq   %rbp            # save base pointer 
26        movq    %rsp, %rbp      # establish our base pointer 
27        addq    $localSize, %rsp  # for local vars. 
28        pushq   %rbx            # save for caller 
29 
30        movl    $queryMsg, %esi 
31        movl    %esi, ptr(%rbp) # point to query message 
32queryLoop: 
33        movl    ptr(%rbp), %esi # current char in string 
34        cmpb    $0, (%esi)  # null character? 
35        je      getResp     # yes, get user response 
36 
37        movl    $1, %edx    # one character 
38        movl    $STDOUT, %edi  # standard out 
39        call    write       # invoke write function 
40 
41        incl    ptr(%rbp)   # ptr++; 
42        jmp     queryLoop   # back to top 
43 
44getResp: 
45        movl    $1, %edx        # read one byte 
46        leaq    response(%rbp), %rsi # into this location 
47        movl    $STDIN, %edi    # from keyboard 
48        call    read 
49# if (response == y’) 
50        cmpb    $y, response(%rbp) # was it y’? 
51        jne     noChange        # no, there is no change 
52 
53# then print the "save" message 
54        movl    $saveMsg, %esi 
55        movl    %esi, ptr(%rbp) # point to message 
56saveLoop: 
57        movl    ptr(%rbp), %esi # current char in string 
58        cmpb    $0, (%esi)  # null character? 
59        je      saveEnd     # yes, leave while loop 
60 
61        movl    $1, %edx    # one character 
62        movl    $STDOUT, %edi  # standard out 
63        call    write       # invoke write function 
64 
65        incl    ptr(%rbp)  # ptr++; 
66        jmp     saveLoop   # back to top 
67 
68saveEnd: 
69        jmp     allDone         # go to end of if-else 
70 
71# else print the "discard" message 
72noChange: 
73        movl    $discardMsg, %esi 
74        movl    %esi, ptr(%rbp) # point to message 
75discardLoop: 
76        movl    ptr(%rbp), %esi # current char in string 
77        cmpb    $0, (%esi)  # null character? 
78        je      allDone     # yes, leave while loop 
79 
80        movl    $1, %edx    # one character 
81        movl    $STDOUT, %edi  # standard out 
82        call    write       # invoke write function 
83 
84        incl    ptr(%rbp)   # ptr++; 
85        jmp     discardLoop   # back to top 
86 
87allDone: 
88        movl    $0, %eax        # return 0; 
89        popq    %rbx            # restore reg. 
90        movq    %rbp, %rsp      # restore stack pointer 
91        popq    %rbp            # restore for caller 
92        ret
Listing 10.10: Get yes/no response from user (programmer assembly language).

The exit from the while loop on line 59

59        je      saveEnd     # yes, leave while loop

jumps to the end of the “then” block of the if-else statement, which then jumps to the end of the entire if-else statement:

68saveEnd: 
69        jmp     allDone         # go to end of if-else

In this particular program we could gain some efficiency by using

        je      allDone     # yes, program done

on line 59. But this very slight efficiency gain comes at the expense of good software engineering. In general, there could be more processing to do after the while loop in the “then” block of the if-else statement. The real danger here is that additional processing will be added during the program’s maintenance phase and the programmer will forget to change the structure. Good, easy to read structure is almost always better than execution efficiency.

Another common programming problem is to check to see if a variable is within a certain range. This requires a compound boolean expression, as shown in the C program in Listing 10.11.

 
1/* 
2 * range.c 
3 * Checks to see if a character entered by user is a numeral. 
4 * Bob Plantz - 12 June 2009 
5 */ 
6 
7#include <unistd.h> 
8 
9int main() 
10{ 
11    char response;  // For users response 
12    char* ptr;      // For text messages 
13 
14    ptr = "Enter single character:  "; 
15    while (*ptr != \0) 
16    { 
17        write(STDOUT_FILENO, ptr, 1); 
18        ptr++; 
19    } 
20 
21    read(STDIN_FILENO, &response, 1); 
22 
23    if ((response <= 9) && (response >= 0)) 
24    { 
25        ptr = "You entered a numeral.\n"; 
26        while (*ptr != \0) 
27        { 
28            write(STDOUT_FILENO, ptr, 1); 
29            ptr++; 
30        } 
31    } 
32    else 
33    { 
34        ptr = "You entered some other character.\n"; 
35        while (*ptr != \0) 
36        { 
37            write(STDOUT_FILENO, ptr, 1); 
38            ptr++; 
39        } 
40    } 
41    return 0; 
42}
Listing 10.11: Compound boolean expression in an if-else construct (C).

Each condition of the boolean expression generally requires a separate comparison/conditional jump pair. The best way to see this is to study the compiler-generated assembly language code of the numeral checking program in Listing 10.12.

 
1        .file  "range.c" 
2        .section      .rodata 
3.LC0: 
4        .string"Enter single character:  " 
5.LC1: 
6        .string"You entered a numeral.\n" 
7        .align 8 
8.LC2: 
9        .string"You entered some other character.\n" 
10        .text 
11        .globlmain 
12        .type  main, @function 
13main: 
14        pushq  %rbp 
15        movq  %rsp, %rbp 
16        subq  $16, %rsp 
17        movq  $.LC0, -8(%rbp) 
18        jmp    .L2 
19.L3: 
20        movq  -8(%rbp), %rax 
21        movl  $1, %edx 
22        movq  %rax, %rsi 
23        movl  $1, %edi 
24        call  write 
25        addq  $1, -8(%rbp) 
26.L2: 
27        movq  -8(%rbp), %rax 
28        movzbl(%rax), %eax 
29        testb  %al, %al 
30        jne    .L3 
31        leaq  -9(%rbp), %rax 
32        movl  $1, %edx 
33        movq  %rax, %rsi 
34        movl  $0, %edi 
35        call  read 
36        movzbl-9(%rbp), %eax   # load numeral character 
37        cmpb  $57, %al         # is numeral > ’9’? 
38        jg    .L4              # yes, go to else part 
39        movzbl-9(%rbp), %eax   # load numeral character 
40        cmpb  $47, %al         # is numeral <= ’/’? 
41        jle    .L4              # yes, go to else part 
42        movq  $.LC1, -8(%rbp)  # "then" part 
43        jmp    .L5 
44.L6: 
45        movq  -8(%rbp), %rax 
46        movl  $1, %edx 
47        movq  %rax, %rsi 
48        movl  $1, %edi 
49        call  write 
50        addq  $1, -8(%rbp) 
51.L5: 
52        movq  -8(%rbp), %rax 
53        movzbl(%rax), %eax 
54        testb  %al, %al 
55        jne    .L6 
56        jmp    .L7              # skip over "else" part 
57.L4:                             # "else" part 
58        movq  $.LC2, -8(%rbp) 
59        jmp    .L8 
60.L9: 
61        movq  -8(%rbp), %rax 
62        movl  $1, %edx 
63        movq  %rax, %rsi 
64        movl  $1, %edi 
65        call  write 
66        addq  $1, -8(%rbp) 
67.L8: 
68        movq  -8(%rbp), %rax 
69        movzbl(%rax), %eax 
70        testb  %al, %al 
71        jne    .L9 
72.L7:                             # end of if-else construct 
73        movl  $0, %eax 
74        leave 
75        ret 
76        .size  main, .-main 
77        .ident"GCC: (Ubuntu/Linaro 4.7.0-7ubuntu3) 4.7.0" 
78        .section      .note.GNU-stack,"",@progbits
Listing 10.12: Compound boolean expression in an if-else construct (gcc assembly language).

In particular, notice that the decision regarding whether the character entered by the user is a numeral or not is made on the lines:

36        movzbl-9(%rbp), %eax   # load numeral character 
37        cmpb  $57, %al         # is numeral > ’9’? 
38        jg    .L4              # yes, go to else part 
39        movzbl-9(%rbp), %eax   # load numeral character 
40        cmpb  $47, %al         # is numeral <= ’/’? 
41        jle    .L4              # yes, go to else part 
42        movq  $.LC1, -8(%rbp)  # "then" part

Consulting Table 2.3 on page 49 we see that the program first compares the character entered by the user with the ascii code for the numeral “9” (5710 = 3916). If the character is numerically greater, the program jumps to .L5, which is the beginning of the “else” part. Then the character is compared to the ASCII code for the character “/”, which is numerically one less that the ascii code for the numeral “0” (4810 = 3016). If the character is numerically equal to or less than, the program also jumps to .L5.

If neither of these conditions causes a jump to the “else” part, the program simply continues on to execute the “then” part. At the end of the “then” part, the program skips over the “else” part to the end of the program:

56        jmp    .L7              # skip over "else" part 
57.L4:                             # "else" part

10.2.1 Short-Circuit Evaluation

Consider the boolean expression use for the if-else conditional:

22   if ((response <= 9) && (response >= 0)) {

On lines 35 and 36 in the assembly language,

35        cmpb  $57, %al         # is numeral > ’9’? 
36        jg    .L5              # yes, go to else part

we see that the test for ’0’ is never made if (response <= ’9’) is false.

This is called short-circuit evaluation in C/C++. When connecting boolean tests with the && and || operators, each of the boolean tests is executed one at a time from left to right. If the overall result of the expression — true or false — is known before all the tests are made, the remaining tests are not executed. This is one of the most important reasons for not writing boolean expressions that include side effects; the operation that produces a needed side effect may never get executed.

10.2.2 Conditional Move

Many binary decisions are very simple. For example, the decision in Listing 10.7 could be written:

    ptr = "Changes discarded.\n"; 
    if (response == y) 
    { 
        ptr = "Changes saved.\n"; 
    } 
    while (*ptr != \0) 
    { 
        write(STDOUT_FILENO, ptr, 1); 
        ptr++; 
    }

This code segment assigns an address to the ptr variable. If the condition, response == ’y’, is true, then the address in the ptr variable is written over with another address. This could be written in assembly language (see Listing 10.10) as:

        movl    $discardMsg, %esi 
# if (response == y’) 
        cmpb    $y, response(%rbp)  # was it y’? 
        jne     noChange        # no, there is no change 
        movl    $saveMsg, %esi  # yes, get other message 
noChange: 
        movl    %esi, ptr(%rbp) # point to message 
msgLoop: 
        movl    ptr(%rbp), %esi # current char in string 
        cmpb    $0, (%esi)  # null character? 
        je      allDone     # yes, leave while loop 
 
        movl    $1, %edx    # one character 
        movl    $STDOUT, %edi  # standard out 
        call    write       # invoke write function 
 
        incl    ptr(%rbp)   # ptr++; 
        jmp     msgLoop     # back to top

The x86-64 architecture provides a conditional move instruction, cmovcc, for simple if constructs like this. The general format is

cmovcc source, destination

where cc is a 1 – 4 letter sequence specifying the settings of the condition codes. Similar to the conditional jump instructions, the conditional data move takes place if the status flag settings are true, and does not if they are false.

Possible letter sequences are the same as for the conditional jump instructions listed in Table 10.1 on page 679. The source operand can be either a register or a memory location, and the destination must be a register. Unlike other data movement instructions, the cmovcc instruction does not use the operand size suffix; the size is implicitly specified by the size of the destination register.

The conditional move instruction would allow the above assembly language to be written with a cmove instruction, where the “e” means “equal” (see Table 10.1).

        movl    $discardMsg, %esi # load addresses of 
        movl    $saveMsg, %edi    #   both messages 
# if (response == y’) 
        cmpb    $y, response(%rbp) # was it y’? 
        cmove   %edi, %esi      # yes, "save" message 
        movl    %esi, ptr(%rbp) # point to message 
msgLoop: 
        movl    ptr(%rbp), %esi # current char in string 
        cmpb    $0, (%esi)  # null character? 
        je      allDone     # yes, leave while loop 
 
        movl    $1, %edx    # one character 
        movl    $STDOUT, %edi  # standard out 
        call    write       # invoke write function 
 
        incl    ptr(%rbp)   # ptr++; 
        jmp     msgLoop     # back to top

Although this actually increases the average number of instructions executed, it allows the CPU to make more efficient use of the pipeline. So a conditional move may provide faster program execution by eliminating possible pipeline inefficiencies caused by a conditional jump. See for example [28], [31], and [34].

10.3 Instructions Introduced Thus Far

This summary shows the assembly language instructions introduced thus far in the book. The page number where the instruction is explained in more detail, which may be in a subsequent chapter, is also given. This book provides only an introduction to the usage of each instruction. You need to consult the manuals ([2][6], [14][18]) in order to learn all the possible uses of the instructions.

10.3.1 Instructions

data movement:
opcode source destination action page





cbtw convert byte to word, al ax 696





cwtl convert word to long, ax eax 696





cltq convert long to quad, eax rax 696





cmovcc %reg/mem %reg conditional move 706





movs $imm/%reg %reg/mem move 506





movs mem %reg move 506





movsss $imm/%reg %reg/mem move, sign extend 693





movzss $imm/%reg %reg/mem move, zero extend 693





popw %reg/mem pop from stack 566





pushw $imm/%reg/mem push onto stack 566










s = b, w, l, q; w = l, q; cc = condition codes

arithmetic/logic:
opcode source destination action page





adds $imm/%reg %reg/mem add 607





adds mem %reg add 607





cmps $imm/%reg %reg/mem compare 676





cmps mem %reg compare 676





decs %reg/mem decrement 699





incs %reg/mem increment 698





leaw mem %reg load effective address 579





subs $imm/%reg %reg/mem subtract 612





subs mem %reg subtract 612





tests $imm/%reg %reg/mem test bits 676





tests mem %reg test bits 676










s = b, w, l, q; w = l, q

program flow control:
opcode location action page




call label call function 546




ja label jump above (unsigned) 683




jae label jump above/equal (unsigned) 683




jb label jump below (unsigned) 683




jbe label jump below/equal (unsigned) 683




je label jump equal 679




jg label jump greater than (signed) 686




jge label jump greater than/equal (signed) 686




jl label jump less than (signed) 686




jle label jump less than/equal (signed) 686




jmp label jump 691




jne label jump not equal 679




jno label jump no overflow 679




jcc label jump on condition codes 679




leave undo stack frame 580




ret return from function 583




syscall call kernel function 587








cc = condition codes

10.3.2 Addressing Modes

__________________________________________________________

register direct:

The data value is located in a CPU register.

syntax: name of the register with a “%” prefix.

example: movl    %eax, %ebx



immediate data:

The data value is located immediately after the instruction. Source operand only.

syntax: data value with a “$” prefix.

example: movl    $0xabcd1234, %ebx



base register plus offset:

The data value is located in memory. The address of the memory location is the sum of a value in a base register plus an offset value.

syntax: use the name of the register with parentheses around the name and the offset value immediately before the left parenthesis.

example: movl    $0xaabbccdd, 12(%eax)



rip-relative:

The target is a memory address determined by adding an offset to the current address in the rip register.

syntax: a programmer-defined label

example: je     somePlace



10.4 Exercises

10-1

10.1) Verify on paper that the machine instructions in Table 10.4 actually cause a jump of the number of bytes shown (in decimal) when the jump is taken.

10-2

10.1) Enter the program in Listing 10.2 and verify that the jump to here1 uses the rip-relative addressing mode, and the other two jumps use the direct address. Hint: Produce a listing file for the program and use gdb to examine register and memory contents.

10-3

10.1) Enter the program in Listing 10.5, changing the while loop to use eax as a pointer:

        movl    $theString, %eax 
whileLoop: 
        cmpb    $0, (%eax)  # null character? 
        je      allDone     # yes, all done 
 
        movl    $1, %edx    # one character 
        movl    %eax, %esi  # current pointer 
        movl    $STDOUT, %edi  # standard out 
        call    write       # invoke write function 
 
        incl    %eax        # aString++; 
        jmp     whileLoop   # back to top

This would seem to be more efficient than reading the pointer from memory each time through the loop. Use gdb to debug the program. Set a break point at the call instruction and another break point at the incl instruction. Inspect the registers each time the program breaks into gdb. What is happening to the value in eax? Hint: Read what the “man 2 write” shell command has to say about the write system call function. This exercise points out the necessity of understanding what happens to registers when calling another function. In general, it is safer to use local variables in the stack frame.

10-4

10.1) Assume that you do not know how many numerals there are, only that the first one is ’0’ and the last one is ’9’ (the character “0” and character “9”). Write a program in assembly language that displays all the numerals, 0 – 9, on the screen, one character at a time. Use only one byte in the .data segment for storing a character; do not allocate a separate byte for each numeral.

10-5

10.1) Assume that you do not know how many upper case letters there are, only that the first one is ’A’ and the last one is ’Z’. Write a program in assembly language that displays all the upper case letters, A – Z, on the screen, one character at a time. Use only one byte in the .data segment for storing a character; do not allocate a separate byte for each numeral.

10-6

10.1) Assume that you do not know how many lower case letters there are, only that the first one is ’a’ and the last one is ’z’. Write a program in assembly language that displays all the lower case letters, a – z, on the screen, one character at a time. Use only one byte in the .data segment for storing a character; do not allocate a separate byte for each numeral.

10-7

10.1) Enter the following C program and use the “-S” option to generate the assembly language:

 
1/* 
2 * forLoop.c 
3 * For loop multiplication. 
4 * 
5 * Bob Plantz - 21 June 2009 
6 */ 
7 
8#include<stdio.h> 
9 
10int main () 
11{ 
12    int x, y, z; 
13    int i; 
14 
15    printf("Enter two integers: "); 
16    scanf("%i %i", &x, &y); 
17    z = x; 
18    for (i = 1; i < y; i++) 
19        z += x; 
20 
21    printf("%i * %i = %i\n", x, y, z); 
22    return 0; 
23}
Listing 10.13: Simple for loop to perform multiplication.

Identify the loop that performs the actual multiplication. Write an equivalent C program that uses a while loop instead of the for loop, and also generate the assembly language for it. Do the loops differ? If so, how?

10-8

10.2) Enter the C program in Listing 10.7 and get it to work. Do you see any odd behavior when the program terminates? Can you fix it? Hint: When the program prompts the user, how many keys did you press? What was the second key press?

10-9

10.2) Enter the program in Listing 10.10 and get it to work.

10-10

10.2) Write a program in assembly language that displays all the printable characters that are neither numerals nor letters on the screen, one character at a time. Don’t forget that the space character,  ’, is printable. Do not display the DEL character. Use only one byte for storing a character; do not allocate a separate byte for each character.

Use only one while loop in this program. You will need an if-else construct with a compound boolean conditional statement.

10-11

10.2) Write a program in assembly language that

a)

prompts the user to enter a text string,

b)

reads the user’s input into a char array,

c)

echoes the user’s input string,

d)

increments each character in the string to the next character in the ASCII sequence, with the last printable character “wrapping around” to the first printable character, and

e)

displays the modified string.

10-12

10.2) Write a program in assembly language that

a)

prompts the user to enter a text string,

b)

reads the user’s input into a char array,

c)

echoes the user’s input string,

d)

decrements each character in the string to the previous character in the ASCII sequence, with the first printable character “wrapping around” to the last printable character, and

e)

displays the modified string.

10-13

10.2) Write a program in assembly language that

a)

instructs the user,

b)

prompts the user to enter a character,

c)

reads the user’s input into a char variable,

d)

if the user enters a ’q’, the program terminates,

e)

if the user enters a numeral, the program echoes the numeral the number of times represented by the numeral plus one, and

f)

any other printable character is echoed just once.

The program continues to run until the user enters a ’q’.

For example, a run of the program might look like (user input is boldface):

A single numeral, N, is echoed N+1 times, other characters are echoed once. ’q’ ends program.
Enter a single character: a
You entered: a
Enter a single character: Z
You entered: Z
Enter a single character: 5
You entered: 5
You entered: 5
You entered: 5
You entered: 5
You entered: 5
You entered: 5
Enter a single character: %
You entered: %
Enter a single character: q
End of program.