4.4 Programming Style

Your style of writing assembly language programs is almost as important as your accuracy. Good habits in layout, selection of symbolic names, and appropriate and illuminating comments help you to program correctly and easily. Good programming habits also make your programs much easier for your TA to read and grade. Here are two examples of the same NASM program to illustrate good and poor programming styles:

; MP99
;  Sharon Sharp
;  02-23-2001
;
;   This program copies the word contents of list1 to list2

;     Enter with:
;       SI = offset of List1
;       DI = offset of List2
;
;     Assumes:
;       Both lists are in DS
;       List end marker = 0
;
SEGMENT code

        GLOBAL LstCpy

LstCpy
        push    ax              ; AX used for temp
.lp:
        mov     ax, [si]        ; fetch word and test for zero
        test    ax, ax
        jz      .exit
        mov     [di], ax
        add     si, 2           ; advance and recycle
        add     di, 2
        jmp     .lp
.exit:
        pop     ax
        ret

Here the symbols help to define the program structure and the variables. The comments are inserted only where relevant and give functional rather than trivial instruction explanations. The open-spacing and orderly structure of the program also suggests the various parts. In addition, the listing to be handed in has a useful title.

The following program is a different kettle of fish:

segment        code
cc
 PUSH ax
    global cc
A:  mov ax,[si]
        CMP      AX,0
   je   b       ;
 mov    [di],ax;          move ax to list
  add si,2;     incr si
        ADD     di,2
 jmp a
b:              pop ax
 ret

This is a fairly extreme example of muddling up a program. The variables are hard to identify; the comments are thoroughly mixed with the instructions and only explain instructions not their function. Amazingly enough, NASM will still assemble this example to a working program, but it is almost incomprehensible to humans, including your teaching assistants.

Here is a complete program written to illustrate good programming style:

; Example Program
;  M. C. Loui
;  29 June 1986
;
; Translated to NASM
;  Peter L. B. Johnson
;  2001
;
; This boring program illustrates good programming style. The user
; types a list of names separated by carriage returns; if the user
; presses ESC before the return, then that name is canceled. The
; program uses the extremely inefficient bubble sort algorithm to
; sort the names into lexicographic order and prints them out.

        CR      equ     0Dh
        LF      equ     0Ah
        BEL     equ     07h
        ESCKEY  equ     1Bh
        MAXNUM  equ     20      ; Maximum number of names
        MAXLEN  equ     80      ; Maximum name length

        EXTERN  kbdine, dspout, dspmsg, dosxit

SEGMENT stkseg STACK CLASS=STACK ; **** STACK SEGMENT ****
        resb    64*8
stacktop:

SEGMENT code                    ; **** CODE SEGMENT ****

Array   times MAXNUM*MAXLEN db '$'      ; Array of names
NumNam  dw      0               ; Number of names input
HdMsg   db      'Type up to 20 names separated by carriage returns','$'

..start:
        mov     ax, cs
        mov     ds, ax          ; Initialize DS and ES registers
        mov     es, ax
        mov     ax, stkseg      ; Initialize Stack Segment
        mov     ss, ax
        mov     sp, stacktop    ; Initialize Stack Pointer

        mov     dx, HdMsg
        call    dspmsg
        call    InNames         ; Input the names
        call    Sort            ; Sort them
        call    OutNames        ; Output the names
        call    dosxit

;
; Subroutine InNames
; Reads in list of up to 20 names from the user and stores them in Array.
;      Output: NumNam - Number of names typed in
;      Calls:  GetOne
;
InNames
        push    bx
        mov     word [NumNam], 0        ; Initialize NumNam
        mov     bx, Array               ; BX points to next name
.lp:
        call    GetOne                  ; Input one name
        cmp     byte [GetStat], 0       ; Done if user typed only
        je      .done                   ;  a carriage return
        add     bx, MAXLEN              ; Point to next name
        inc     word [NumNam]
        cmp     word [NumNam], MAXNUM   ; Continue only if
        jl      .lp                     ;  NumNam < MAXNUM
.done:
        pop     bx
        ret

;
; Subroutine GetOne
; Reads in one name of up to MAXLEN characters into Array.
;      Input:  BX - Offset of name in Array
;      Output: GETSTAT - Status of call
;                      = 1 if normal return
;                      = 0 if user typed only carriage return
;      Calls:  dspmsg, kbdine, dspout
;
GetStat resb    1
Prompt  db      CR,LF,':$'
GetOne
        push    si
        push    dx
        push    ax
.lp1:
        xor     si, si
        mov     dx, Prompt              ; Prompt user for name
        call    dspmsg
.lp2:
        call    kbdine                  ; Get next character into AL
        cmp     al, CR
        je      .cr
        cmp     al, ESCKEY
        je      .esc
        mov     byte [bx+si], al        ; Store character
        inc     si
        jmp     short .lp2
.esc:
        mov     dl, BEL                 ; If user typed ESC
        call    dspout                  ;  then ring bell
        jmp     short .lp1              ;  and restart this name
.cr:
        mov     byte [GetStat], 1       ; If user typed only CR
        test    si, si
        jnz     .exit
        mov     byte [GetStat], 0       ;  then set GetStat to 0
.exit:
        pop     ax
        pop     dx
        pop     si
        ret

;
; Subroutine Sort
; Sorts Array using the bubble sort algorithm.
; WARNING: This algorithm is extremely inefficient.
;      Input:  NumNam - Number of names in Array
;      Calls:  CmpNam, XChgNam
;
I       resw    1                       ; Index into Array
J       resw    1                       ; Index into Array
Len     resw    1                       ; Length of each name
Sort
        push    ax
        push    dx
        push    si
        push    di

        mov     word [Len], MAXLEN
        mov     ax, [NumNam]            ; Initialize I
        dec     ax                      ;  to NumNam - 1
        mov     [I], ax                 ; for I=N-1 down to 1 do
.loopi:
        cmp     word [I], 1
        jl      .exit
        mov     word [J], 1             ;  for J=1 to I do
.loopj:
        mov     ax, [J]
        cmp     ax, [I]                 ;  (cannot cmp [J],[I])
        jg      .deci
        dec     ax                      ;   compute offset of
        mul     word [Len]              ;    Jth name
        add     ax, Array
        mov     si, ax                  ;   SI = offset of Jth name
        mov     di, ax
        add     di, [Len]               ;   DI = offset of J+1st name
        call    CmpNam
        cmp     byte [Result], 0        ;   if Jth name > J+1st name
        jle     .incj
        call    XChgNam                 ;    then exchange them
.incj:
        inc     word [J]
        jmp     short .loopj            ;  end for
.deci:
        dec     word [I]
        jmp     short .loopi            ; end for
.exit:
        pop     di
        pop     si
        pop     dx
        pop     ax
        ret

;
; Subroutine CmpNam
; Compares two names and determines which is lexicographically larger.
;       Inputs:  SI, DI - pointers to two names
;       Outputs: Result = -1 if name at SI is before name at DI
;                       = 0  if name at SI equals name at DI
;                       = 1  if name at SI is after name at DI
;
Result  resb    1
CmpNam
        push    ax
        push    cx
        push    si
        push    di

        mov     cx, MAXLEN              ; Length of names
.lp:
        mov     al, [si]                ; Compare next bytes
        cmp     al, [di]
        ja      .a
        jb      .b
        inc     si
        inc     di
        loop    .lp
        mov     byte [Result], 0        ; If all bytes equal, then names
        jmp     short .exit             ;  equal.
.a:
        mov     byte [Result], 1        ; Here if name at SI before name at
        jmp     short .exit             ;  DI.
.b:
        mov     byte [Result], -1       ; Here if name at SI after name at
.exit:                                  ;  DI.
        pop     di
        pop     si
        pop     cx
        pop     ax
        ret

;
; Subroutine XChgNam
; Exchanges the names pointed to by SI and DI.
;       Inputs:  SI, DI - pointers to two names
;
XChgNam
        push    ax
        push    cx
        push    si
        push    di

        mov     cx, MAXLEN
.lp:
        mov     al, [si]                ; Exchange bytes pointed to
        xchg    al, [di]                ;  by SI and DI
        xchg    al, [si]
        inc     si
        inc     di
        loop    .lp

        pop     di
        pop     si
        pop     cx
        pop     ax
        ret

;
; Subroutine OutNames
; Prints out names from Array
;       Inputs:  NumNam - Number of names in Array
;       Calls:   dspmsg
;
CRLF    db      CR, LF, '$'
OutNames
        push    bx
        push    cx
        push    dx

        mov     cx, [NumNam]            ; Number of names to output
        mov     bx, Array               ; BX holds offset of next name
.lp:
        mov     dx, bx
        call    dspmsg
        mov     dx, CRLF                ; CR and LF to separate
        call    dspmsg                  ;  adjacant names
        add     bx, MAXLEN
        loop    .lp

        pop     dx
        pop     cx
        pop     bx
        ret