Style | Efficiency | Suggestions | Alignment | Source code | Comments | Hungarian notation | IFDEF | Example | Directives | Consistency | Notes | About | Home

Mascotr_harvey


MASM Programming Style

This chapter discusses how writing style and work habits affect the quality and maintainability of our code. Parts are based on an earlier book; the principles apply in this 32- heading-toward 64-bit, mixed-language, mega-productivity world, but I'll reserve many of today's techniques for print.

Each year the bar moves up a notch or two, forcing us to constantly hone our style — make it more maintainable, readable, reusable, but write it faster. That's why languages like C++ dominate, and Assembly language languishes. If we Assembly language programmers are to compete, we have to develop high-level attitudes about style and reusability.

When searching for a text formatting style guide, look to professionals — people who turn out thousands of lines of reusable code. While writing this style guide, I compared code written by Hewlett-Packard, Lotus, Microsoft and by prominent individuals. I did not look at code from computer magazines, since sample source code printed in magazines is written for readability in four point type, and to pack a lot of code on the page, not for 80-column displays and maintainability.

One pattern emerged: simple, consistent, ego-free, well documented code. There is room for personality — the source code listing of one HP computer operating system includes a novelette along the right margin of the ten megabytes of source code — but no useless baggage or quirky, non-standard formatting within the code.

Efficiency

There are only a few secrets to writing good, maintainable Assembly language: time, peace of mind, access to information, and a big monitor. To keep code manageable, we try to reduce complexity so that a single logical section is no longer than one window-full. If the source code doesn't fit, try to reduce the complexity — or get a bigger screen.

Even if you're writing text-mode applications, you will find that graphical operating systems and editors are great places to work. High-resolution video cards help you see more, and built-in crash protection and task-switching let you go through the edit-test-crash routine without hitting the red switch too often.

Consistent work habits make consistent production smoother. Some habits, such as maintaining redundant backup copies, can save even more. For example, while editing one revision of this book, one hard disk quickly went crazy, but since a regular backup scheme was in place, only one file was lost (which was, dumbly, placed on the Microsoft Windows desktop).

Source Code Suggestions

First, a few general ideas about developing work habits:

Text Alignment

Microsoft writes clean, readable code — or they did in the '90s, when some of their stars still wrote in Assembly language. In their samples, tabs are frequently turned into spaces to eliminate column mis-alignment and formatting changes in the user's editor; still, nearly everything is tab-aligned.

While C and Basic often assume tabs every three or four spaces, Assembly language tabs are nearly always 8 spaces. This is a hold-over from the days of terminals and fixed-pitch line printers; it's also a convention encouraged by the length and syntax of Assembler mnemonics.

Indent one tab stop to the instruction, then a second tab stop to the modifier. Comments begin at uniform tab stops, which most often ends-up at column 41. The advantage is that you get more room for comments. Large comment blocks are usually indented to the first tab stop. Here's how tabs expand when you set tab stops at 8 spaces:

        mov     ax, OFFSET stuff        ; Comments are aligned
.................................................................
|       |       |       |       |       |       |       |       |
1       9       17      25      33      41      49      57      65

Indenting code beyond the first tab stop would cause clumping, with large empty areas, followed by code and comments jammed-together on the right.

Simple code often aligns comments at column 33, while code containing lots of long references to labels or groups often have comment blocks indented to column 49. Most often, comments are aligned at column 41. These aren't arbitrary numbers, but result of setting tab stops at eight characters.

I prefer real tab characters because they format consistently, save disk space (files are usually one-third smaller), cut editor load time and speed assembly. Formatting with tab characters helps keep text aligned as you change code or comments — keeping text formatted is less fussy.

Carriage Return Required at End of Files

Gotcha: Always end the last line in the source code file with a carriage return (preferably a linefeed, too). Some versions of MASM will cause an "Unexpected end of file" error if the file ends with END without the carriage return. A CTRL+Z character end-of-file marker is optional, not required, and should be avoided for compatibility with newer editors and platforms.

Capitalization

Pre-processor directives are usually upper-case, while instructions are lower case. To distinguish macro equivalents from directives, they are usually in mixed-case. The C/C++ convention is to enter constants (equates) in upper case, but mixed case is usually more readable.

.MODEL medium
.DATA
Msg	DB "Text string"
.CODE 
QUACK  PROC PUBLIC
        enter   0, 0             ; May be an instruction or a macro
        mov     bx, OFFSET DGROUP:Msg
        mov     bx, [bp+6]
        mov     WORD PTR [bx], ax
        leave
        ret    2
QUACK  ENDP
END

Lower-case text is easier to read on cramped screens, such as 50-line VGA text mode or Windows-based editors. While upper case text fills the character cell, lower case text leaves several pixels blank, adding visual clues to separate lines.

Adding Tabs To Editors

Most professional editors can be coerced into saving files in plain text format, usually with tabs preserved. The feature is frequently disabled by default, since C/C++ code is usually indented with three or four spaces. In the mid-90s, most text editors were primitive; today's editors are customizable and complete; I won't recommend one, since the one I'm using today will likely not be the one I'm using when you read this — I'm not married to my editor.

Microsoft PWB

PWB (Programmer's Workbench) is a versatile antique, designed for the MS-DOS operating environment. Microsoft's PWB (furnished with Basic PDS 7.0 and 7.1, MASM 5.1 through 6.x, and Microsoft C/C++ 5.1 through 7.0) has a vast array of options, most of which default to very strange settings. Luckily, PWB is easy to customize by changing the TOOLS.INI file. To force PWB to use real tabs with Assembly language source files, add these lines (without the remarks) to TOOLS.INI:

[pwb-.ASM .INC .LST]
filetab:8           ; Expand tabs to 8 spaces
graphic:tab         ; Make the Tab key insert tab characters
realtabs:yes        ; Use real tab characters
tabalign:yes        ; Don't let the cursor stop within a tab

Tabbing Visual C++

If you're working in Microsoft Windows, you may want to try the Visual Workbench editor (VWB.EXE) furnished with 1.x versions of Microsoft Visual C++. You can tell VWB to preserve tabs by following these steps:

Microsoft, by their own reckoning, introduces new versions of their C/C++ products about every ten months; each iteration includes a larger, more complex editor. Visual C++ 4.x and Fortran Power Station included yet another editor, called the Microsoft Developer Studio (MSDEV.EXE). Here's how to preserve tab characters:

Tip: You don't have to use a programmer's editor. Microsoft Word for MS-DOS has always accepted tabs and saved files in plain ASCII format (you will have to change the default tab stop from 0.5 to 0.8, and place a style sheet that discourages word-wrap in the working directory). Starting with version 6.0, WordPerfect for MS-DOS also loads and saves plain ASCII text.

Assembly Source Code

We express our understanding of computer notation and binary, Intel processors, calling and naming conventions, in source code files. We write in high-level, abstract code representing processor instructions, and the Assembler interprets our source code and creates compiled object code.

Document Your Code

What the manual is to the user, the source code is to the programmer. Formatting aids navigation through source code, and comments explain our code to ourselves.

Mark Code Sections

Major divisions in a file are separated by a repeating line. It really doesn't matter what line drawing characters you use. Microsoft usually uses asterisk ("*") characters (partly because of their C background). I avoid lines of asterisks because I mark unfinished pieces of code and documentation with "***".

If you are editing with fixed-pitch type now, but may later switch to an editor that supports proportional type, avoid repeated characters that are narrow in proportional type. A line of C++ style slashes, for instance, is very narrow. With many fonts, the equals sign is wider than the dash (= instead of -). Here are some repeating characters in fixed-pitch type:

----------------------------------
==================================
**********************************
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Here are the same characters in a proportional font:

----------------------------------
==================================
**********************************
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Blank Lines

A blank line is the cleanest way to separate sections. For especially complex code, you might separate groups of instructions with a second blank line. Too many blank lines and lines that are too long, however, make code difficult to read, especially in a 25-line window.

Some programming books (see Code Complete in the Bibliography) recommend 8- to 16-percent blank lines in high-level language code. Assembly language is like high-level code under a microscope, so the percentage of blank lines is often higher, perhaps 12- to 25-percent blank lines. Logical blocks of code (often called paragraphs) are typically five Assembly instructions or directives, which results in 20-percent blank lines.

Library contains approximately 15-percent blank lines; a higher-level floating point library by this author contains 18-percent blank lines. In this context, we define a blank line as any line containing neither numbers nor words, so a line of dashes is considered blank.

Tip: A line separating sections aids readability. However, a long line can interfere with eye-tracking and make it slower to search for program labels or read comments. Long lines make unnatural breaks that stop the eye — in the left border, they make it difficult to search for the next label; extending too far to the right, they split-up comments, potentially reducing readability.

Document With Labels

It's easier to differentiate between similar labels that are short. If you must use long labels, it will be quicker to distinguish similar long labels if you insert a few unique characters before column eight.

        mov     bx, ax
LabelMaker1:
        xor     ax, ax
LabelMaker2:

Shorter labels are more readable, but long labels still can work:

        mov     bx, ax
Label1Maker:
        xor     ax, ax
Label2Maker:

If a label is longer than 5 characters, place it on a separate line. In most cases, formatting the labels alone helps delimit logically related passages of code. I never put code on the same line as a label.

Tip: When labels aren't helpful — and when you have just written a hundred unique local labels to handle a hundred little conditional tests and never want to see a label again — try anonymous labels. These are the "@@" labels that mark a place, when it doesn't matter what that place is called. Used carefully, anonymous labels make code more readable... and more write-able.

Document With Equates

Equates are more readable than "[BP+6]", and you can assemble the code for another memory model or platform with fewer source code changes.

This example creates the text equate Str1, representing an argument passed on the stack:

IF @CodeSize				; If at least medium model
 Str1   TEXTEQU <WORD PTR [bp+6]>
ELSE					; Otherwise, small model
 Str1   TEXTEQU <WORD PTR [bp+4]>
ENDIF
        mov     bx, Str1      ; Read the data

Tip: Many errors blamed on MASM bugs are related to trying to use reserved words as identifiers. Words like ALIAS, BASIC, C, GOTO, SIZE, STR and TEST are naturals, but they're all reserved by MASM. The error message generally refers to the word following the reserved word. This is logical because test is an instruction, but in TEST SEGMENT, the word SEGMENT is not a valid argument for the test instruction.

Note: The ?Arg macro simplifies declaring arguments. This powerful, versatile macro is described on page ***.

Comments

Comments are the who-what-why-where-when-how of programming. Leave them out, and the story is incomplete. When reading Assembly language source code, we're not only following the flow of the program, but also the state of CPU registers; I believe that high-level coding is a right-brain, abstract task, while hardware is left-brain oriented. Assembly language programming is a battle of the brains.

High-level language programmers use descriptive variable names that stay the same throughout a function. The poor Assembly language programmer has only a handful of registers; the EAX register may mean something entirely different every few lines. A play-by-play discussion of variable changes can make especially hairy passages readable. A running narrative, if you will, helps the reader follow the subtle plot twists and dialog, while avoiding superfluous details and blind alleys.

The most pleasant surprise when we comment code for others, is that we find that we're explaining it to ourselves, too. Well-commented code is a sign that the writer understands the subject.

For readability, use lots of comments, even if they cover obvious points. Explaining an obvious passage of code does not make us look ignorant — perhaps the opposite, especially later when we've forgotten how it works.

Nuisance comments don't help. Don't comment the obvious:

        push    ds            ; Save the DS register

Instead, explain why you're saving the register. When you comment, say something that will help explain the code, or don't say it at all.

Comment Suggestions

Hungarian Notation

Assembly language is not strongly typed. For good reason: EAX may change from a signed integer to text, or binary data, every few lines. MASM 6.x helps us enforce more consistent coding practices than allowed by earlier assemblers, but it's ultimately up to us to create and follow our own rules — and to know when it's time to break those rules. Hungarian Notation, named for Charles Simonyi of Microsoft, is a consistent symbol naming convention, followed by many C programmers and used throughout Microsoft Windows. In Assembly language, we can use Hungarian Notation for parameter names, to help distinguish a pointer to a string descriptor from a pointer to an integer.

In Hungarian Notation, symbol names begin with one or more lower-case characters that describe the data type — to encourage data typing. For instance, hFile is a handle to a file. Here are the common prefixes (words of discipline):

Hungarian Notation Prefixes


PrefixType
bBoolean (16 bits)
byByte (8 bits)
cChar (8 bits, unsigned)
cx, cyCount
dwDword (32 bits)
fnFunction (near/far?)
hHandle (16 bits)
iInteger (16 bits)
lLong integer (32 bits)
lpLong pointer (32-48 bits)
nShort or integer(16-bits signed)
pPointer (16 bits)
sString (8-bits each)
szZero-byte terminated string
wUnsigned integer (16 bits)
x, yShort integer (coordinates)

You can combine symbols to represent complex data types, such as lpsQuack is a long pointer to a string. In an attempt at clarity, Hungarian Notation adds the complexity of a new, arbitrary naming convention.

The advantage touted for Hungarian notation is bug-avoidance. By forcing symbol names to be strongly typed, we're less likely to write a long integer value to a short integer variable. A more significant advantage of Hungarian Notation is discipline. While we may not uniformly adopt these admittedly strict and occasionally impractical practices, just by considering them, we become more consistent and conscious of organized programming methods.

Microsoft now uses unique prefixes for constants (equates). There is no data storage size for constants in most programming languages, so the name identifies the purpose. In Visual Basic for Windows, for instance, constants are preceded by vb, and database object constants begin with db. The convention is designed to prevent duplicate or conflicting names in projects with a large number of constants.

Many C symbols begin with one or more underscore (_) characters, or use character-case differences, in a confusing and misguided attempt at minimizing conflicts. A more useful method is a prefix-word, often an acronym. Microsoft Windows constants separate the prefix from the name with an underscore, for instance COLOR_WINDOW.

Rigid type-casting is arguably just unnecessary baggage. However, consistent, clear naming conventions make strongly-typed names simpler. If you don't adopt Hungarian notation, and we aren't recommending that you do, remember one lesson: clarity.

Using IF and IFDEF

Before version 4.0, Library used no equates, macros, shared code or common data, but it was shorter than 30,000 lines. At the start of the 4.0 upgrade, I cut code size to the low 20's by sharing code and rewriting. It rose to around 40,000 lines, using new-style code and consistent formatting. Now at over 100,000 lines, Library would be unmanageable without high-level tools.

The simplest high-level tools are conditional directives, which allow you to create different sections of code, based on the state of equates. The IF and IFDEF directives tell us if a value is non-zero, or if it exists at all. We often use IFDEF, but it misses simple typographical errors.

Real-World IF Example

In the original Library 4.0 far string conversion, the FarString equate was always defined. This minimized the chance of typos causing the wrong version to be assembled, and ensured consistency. Far string tests looked like this:

IF FarString 
   ; create far string version
ELSE
   ; create near string version
ENDIF

During the 5.0 upgrade (during much of which I was not around, except to add the new Assembly language functions), the programmers changed the convention from using IF to IFDEF. Here is the later method:

IFDEF FarString
   ; create far string version
ELSE
   ; create near string version
ENDIF

Everything broke, and it took weeks to implement. Those who changed everything on 5.0 are now gone; I never learned their names.

I personally believe IFDEF should only be used in boilerplate code or include files, but never within the body of a function. This applies to #ifdef in C/C++, too.

Compiler pre-processors are grammar checkers; when you use IF instead of IFDEF, you are also using the compiler as a spelling checker. In an ideal environment, the following tests would be self-validating:

IF @System EQ FarString?
IF @System EQ FarSting?

Today, the FarString, FarFast and PowerBasic equates are still tested with IFDEF, because it would require a significant amount work and testing to return to safer methods. Newer equates are always defined — and we let MASM help us check for typos.

High-level Example

The following example combines high-level macros, directives, and include files to create a simple, portable, Assembly language function.

INCLUDE a$setup.inc ; Version 3.0 or later
INCLUDE a$fp.inc    ; Floating point macros and equates

; The following two lines list switches to pass to the assembler
;?MASM /DElementSize=8
;?MASM /DElementSize=4
; 
; The following two lines describe the function declaration
;?PROTO MyDblFun:VOID, DblArr:AREAL8, Elements:IWORD
;?PROTO MySngFun:VOID, SngArr:AREAL4, Elements:IWORD
?Code   R87_TEXT                ; Floating-point library code segment name
?Arg    Array:FAR, Elements:IWORD
?ProcD	MyDblFun                ; Create a decorated function name
        enter   0, 0            ; may be a macro or code
        ?IF     <@Model NE Flat?>, <push ds>
        mov     rcx, Elements   ; number of elements in the array
        lpds    rbx, Array      ; DS:BX = the array (model independent)

This code snippet shows two include files, commands buried in comments to be passed to source code interpreters, and high-level, model-independent code. The lines beginning with ;?MASM are interpreted by a program that searches for Assembler switches, then automatically builds make or batch files that will be run to create the library. The ;?Proto lines are generic function descriptions; these are interpreted by a program that automatically creates function declarations for several programming languages.

Virtually every line of code in the function uses high-level macros or equates to isolate us from processor addressing modes and register usage.

High-Level Directives

Purists will tell you that high-level MASM code directives make bad code, that you lose control of code quality, that if they want to write in a high-level language they'll use C, and that the directives are for wimps. The answer to those pleas is simple: "Wanna race? Let's see who comes up with the best code first, and with fewer errors."

Starting with Library 7, new code uses Microsoft MASM 6.x high-level directives whenever possible. They look simple and, when used correctly, create code identical to the hand-wringing tests and conditional jumps with which Assembly language has been burdened since the beginning.

Million-line projects are not uncommon today; high-level code makes it manageable. We encourage using high-level directives because they add type checking, eliminate unnecessary labels, aid readability, and, surprisingly, reduce errors. And, since high-level tests can be nested, we can create complex, nested structures and loops with ease and readability — convoluted code that we would probably otherwise have written in C or Basic because of the complexity.

High-level structures make it simpler to translate code from C or Basic to Assembly language. Conversely, high-level Assembly language is easier to translate to C; this makes us more likely to write complex code in Assembly, knowing that the logic will be portable to other platforms, even if the processor structures are not.

Note: See DIRFIND.ASM, the source code for Library directory read functions, for an example of high-level code and data structures.

Helpful High-Level Directives

High-level directives are essentially C-style conditional tests, handling logic and program flow. Most tests in Assembly language are backwards — if we want to execute a passage of code only if EAX=0, we do a conditional jump if EAX does not equal zero. This backward-thinking is a major cause of logic errors. High-level structures work the right-way around. If EAX=0 then we execute this block of code — high-level structures hide the test, reverse-logic conditional jump, and the label.

Here are some high-level MASM 6.x structures. The simplest is the conditional test; the replacement simply eliminates an arbitrary, occasionally dumb, label, creating identical code:

      .IF(eax > 1)         ; cmp  eax, 1  /  jbe @C0001
           ; conditionally executed code here
      .ENDIF                ; @C0001

The .IF structure can be extended with additional related tests. Keep in mind that .ELSEIF always adds a jmp statement before the test; you may want to use two separate .IF statements instead of .ELSEIF.

      .IF(eax > 1)        ; cmp  eax, 1  /  jbe @C0001
                          ; jmp @C0002
      .ELSEIF (eax > 2)   ; @C0001:  /  cmp eax, 2  /  jb @c0002
      .ENDIF              ; @C0002:

The expression can be fairly complex (though in MASM 6.x, they cannot include references to data structures).

Tip: For clarity, I add parentheses around high-level conditional tests, even though MASM requires them only to change evaluation-precedence. This also makes it easier for software metric programs to evaluate code complexity.

Types Of High-Level Comparisons

Expressions follow C syntax rules, with atrocities like != for not-equal. Here is a list of available test operators:

MASM High-Level Comparisons


OperatorMeaning
==Equal. Two arguments are equivalent.
!=Not equal.
>Greater than.
<=Greater than or equal to.
<Less than.
<=Less than or equal to.
&Bit test. Binary OR test. (Assembles a test instruction).
!Logical NOT.
&&Logical AND. Combines two tests.
||Logical OR. Combines two tests.
Carry?Carry flag is set.
Overflow?Overflow flag is set.
Parity?Parity flag is set.
Sign?Sign flag is set.
Zero?Zero flag is set.

Use parentheses to enforce mathematical priority. Combine tests with the C-style || (logical or), && (logical and) or ! (logical not). We won't discuss every nuance, because syntax rules will likely be extended in later MASM versions.

Predefined directives make it possible to test the carry flag (the Carry? directive) and the zero flag (with Zero?). There is no "Equal?" comparison; it's equivalent to Zero?. You can invert the meaning of a test with the not operator; for instance, "! Carry?" means the carry flag is clear.

      .IF Carry?            ; jnc @C0001
      .ENDIF                ; @C0001:

The most graceful high-level structure simply replaces the Intel 80x86 loop instruction.

      .REPEAT               ; @C0001:
      .UNTILCXZ             ; loop @C0001

The .UNTILCXZ directive accepts an optional simple test — one that evaluates to a zero or non-zero result.

      .REPEAT               ; @C0002:
      .UNTILCXZ ax == 5     ; cmp ax, 5  /  loopne @C0002

Any test more complex than a simple comparison causes MASM to display the "error A2062: expression too complex for .UNTILCXZ" message.

High-Level Directive Examples

Consider the simple task of converting a register to upper-case; we could do it the old fashioned, manual way:

      cmp     al, "a"
      jb      NotUpper
      cmp     al, "z"
      ja      NotUpper
      sub     al, 32
NotUpper:

Or we could make it easier to read with C-like syntax:

      .IF (al >= "a") && (al <= "z")
         sub    al, 32
      .ENDIF

Both examples make equivalent Assembly language code. The wonderful part of high-level directives is readability without labels. You can wend your way, switching between C and MASM-style syntax, to make the project readable-and write-able. If you're not a C programmer, the C-like syntax may be unnerving at first; it's worth the effort — the world is mostly C and Java, anyway.

The greatest savings and convenience come with nested conditional tests:

      .IF (eax > 4)           ; cmp eax,4 / jbe Lbl3
          .IF (ecx == 7)      ; cmp ecx,7 / jne Lbl1
              mov    eax, 1
                              ; jmp Lbl2
          .ELSE               ; Lbl1:
              mov     edx, 9
          .ENDIF              ; Lbl2:
      .ENDIF                  ; Lbl3:

When used with care, these directives always make the same code — it's just more readable. Experiment, see how the directives translate into code. Don't, for instance, use .IF CX != 0 if you really want jcxz, because the code translates to testing if CX == 0 first.

Tip: Remember that comparisons are unsigned unless you specify signed tests.

The .ELSEIF (or .ELSE .IF) directive is rarely useful because it causes a jmp to the end of the structure to be placed before the test.

      .IF (ax != dx)          ; cmp ax,bx / je Lbl1
              mov     bx, 0
                              ; jmp Lbl2
      .ELSE                   ; Lbl1:
              mov     bx, 1
      .ENDIF                  ; Lbl2:

Instead of .ELSEIF, we usually use a series of .IF / .ENDIF structures.

High-Level Capitalization

As an early convert to MASM high-level structures, I applied capitalization conventions I'd always used. This means pre-processor directives, including the .IF structures, are entered in upper-case. Several years and thousands of lines of code later, I now realize this may be an error that may cause readability problems. Perhaps high-level code should look like processor instructions, because it really is. Formatting could be best used to help preserve abstraction, not make it stand-out.

High-Level Directive Cautions

When you first make the transition to high-level directives, have the Assembler create file listings (try the ML /Fl /Sa switches) of every comparison. Compare the code created to your source code; you'll soon begin to feel the relationships, and begin to see high-level directives as shorthand memos to the pre-processor.

You'll see the benefits of high-level structures in the program listings. You'll also find a few problems and oversights:

High-Level Syntax Comparison Tests Are Inconsistent

Gotcha: While modern high-level tests follow C-syntax conventions, pre-processor directives still follow old-style MASM 5.1 conventions. So, while ".IF UserVar > 0" follows C syntax, the pre-processor would ask for "IF UserEquate GT 0". There was talk of fixing these inconsistencies back in '92, when MASM was still a retail product; if it happens now, it'll likely be a third-party's doing.

.IF CX is Inefficient

Gotcha: Avoid testing if the CX register is zero with high-level instructions. The statement ".IF CX" translates to or CX,CX and a jz instruction instead of a jcxz instruction.

.CONTINUE Jumps to The Test

Gotcha: .CONTINUE jumps to the test, not the bottom of the structure. If the test is at the at the top of the structure, .CONTINUE will jump to the top of the structure.

Avoid .WHILE / .ENDW

Gotcha: The .WHILE directive has two purposes: a continuous loop, and a conditional test. The simple form is a wonderfully useful replacement for an unconditional jump:

      .WHILE 1              ; @C0001:
      .ENDW                 ; jmp @C0001

When used with a run-time test, the .WHILE loop is evaluated at the bottom. This causes the assembler to insert an unconditional jump to the bottom of the loop on the first iteration.

      .WHILE      ax > 4    ; jmp @C0001
                            ; @C0002:
      .ENDW                 ; @C0001:
                            ; cmp ax,4
                            ; ja @C0002

High-level Tests Are Unsigned

Gotcha: While C-like in appearance, MASM high-level directives create un-signed conditional jumps by default. If you need to do signed tests, either declare a variable to be signed, or cast (to use a C term for "coerce") the register as signed.

.DATA
Zot	SDWORD 0
.CODE
      .IF(eax > Zot)       ; Signed compare
      .IF(eax > 4)         ; Un-signed compare
      .IF(SDWORD PTR eax > 4) ; Signed compare

The first test in this example is signed because the data is signed. The second is un-signed because the register is not signed (don't try to compare a register to a signed literal, because it doesn't work). The third test is signed because EAX is declared as signed; it's slightly disconcerting that EAX really isn't a pointer here, but that's a MASM syntax quirk.

When written carefully, high-level constructions make the same code as manual-labeling.

Consistency Payoff

The result of a structured, consistent indentation style will be evident as you revise existing code. Most formatting is designed to ease eye-tracking, which makes code easier to read, and makes it quicker to find important passages.

Footnotes

1 — A programming library that I worked on during the first half of the '90s. The real name is available upon request (if you have a good reason).

Many include files, macros and equates are from tools written by this author for use in other programming libraries.

4, 7, 8 — Version numbers of the programming library began with 4.0 in 1990 (which was essentially related to earlier versions in name only), and ended with 7.0 in 1995. While version 8.0 was mostly completed by 1997, it was never released (sigh).

Top | Notice | Home | | © Copyright 2007 R. E. Harvey, All rights reserved.