Hello Again Professor Hyde,We are currently working on ways to publish this text in a form other than HTML (e.g., Postscript, PDF, Frameviewer, hard copy, etc.). This, however, is a low-priority project. Please do not contact Randall Hyde concerning this effort. When something happens, an announcement will appear on "Randall Hyde's Assembly Language Page." Please visit this WEB site at http://webster.ucr.edu for the latest scoop.
Dallas gave me permission to take orders for the Computer Science 13 Manuals. We would need to take charge card orders. The only cards we take are: Master Card, Visa, and Discover. They would need to send the name, numbers, expiration date, type of card, and authorization to charge $95.00 for the manual and shipping, also we should have their phone number in case the company has any trouble delivery. They can use my e-mail address for the orders and I will process them as soon as possible. I would assume that two weeks would be sufficient for printing, packages and delivery time.
I am open to suggestions if you can think of any to make this as easy as possible.
Thank You for your business,
Kathy Chapman, Assistant
Printing and Reprographics
University of California
includedirectives, and initializes necessary Library routines for you. You should not attempt to create a new program from scratch unless you are very familiar with the internal operation of the Standard Library.
callinstruction for invocation. You cannot, for example, directly
putcroutine. Instead, you invoke the
putcmacro that includes a call to the
sl_putcprocedure ("SL" stands for "Standard Library").
meminitroutine initializes the memory manager and you must call it before any routine that uses the memory manager. Since many Standard Library routines use the memory manager, you should call this procedure early in the program. The "SHELL.ASM" file makes this call for you.
mallocroutine allocates storage on the heap and returns a pointer to the block it allocates in the
es:diregisters. Before calling
mallocyou need to load the size of the block (in bytes) into the
cxregister. On return,
mallocsets the carry flag if an error occurs (insufficient memory). If the carry is clear,
es:dipoints at a block of bytes the size you've specified:
mov cx, 1024 ;Grab 1024 bytes on the heap malloc ;Call MALLOC jc MallocError ;If memory error. mov word ptr PNTR, DI ;Save away pointer to block. mov word ptr PNTR+2, ESWhen you call
malloc, the memory manager promises that the block it gives you is free and clear and it will not reallocate that block until you explicitly free it. To return a block of memory back to the memory manager so you can (possibly) re-use that block of memory in the future, use the
freeexpects you to pass the pointer returned by
les di, PNTR ;Get pointer to free free ;Free that block jc BadFreeAs usual for most Standard Library routines, if the
freeroutine has some sort of difficulty it will return the carry flag set to denote an error.
getc(get a character),
gets(get a string), and
getsm(get a malloc'd string).
Getcreads a single character from the keyboard and returns that character in the
alregister. It returns end of file (EOF ) status in the
ahregister (zero means EOF did not occur, one means EOF did occur). It does not modify any other registers. As usual, the carry flag returns the error status. You do not need to pass
getcany values in the registers.
Getcdoes not echo the input character to the display screen. You must explicitly print the character if you want it to appear on the output monitor.
; Note: "CR" is a symbol that appears in the "consts.a" ; header file. It is the value 13 which is the ASCII code ; for the carriage return character Wait4Enter: getc cmp al, cr jne Wait4EnterThe
getsroutine reads an entire line of text from the keyboard. It stores each successive character of the input line into a byte array whose base address you pass in the
es:diregister pair. This array must have room for at least 128 bytes. The
getsroutine will read each character and place it in the array except for the carriage return character.
Getsterminates the input line with a zero byte (which is compatible with the Standard Library string handling routines).
Getsechoes each character you type to the display device, it also handles simple line editing functions such as backspace. As usual,
getsreturns the carry set if an error occurs. The following example reads a line of text from the standard input device and then counts the number of characters typed. This code is tricky, note that it initializes the count and pointer to -1 prior to entering the loop and then immediately increments them by one. This sets the count to zero and adjusts the pointer so that it points at the first character in the string. This simplification produces slightly more efficient code than the straightforward solution would produce:
DSEG segment MyArray byte 128 dup (?) DSEG ends CSEG segment . . . ; Note: LESI is a macro (found in consts.a) that loads ; ES:DI with the address of its operand. It expands to the ; code: ; ; mov di, seg operand ; mov es, di ; mov di, offset operand ; ; You will use the macro quite a bit before many Standard ; Library calls. lesi MyArray ;Get address of inp buf. gets ;Read a line of text. mov ah, -1 ;Save count here. lea bx, -1[di] ;Point just before string. CountLoop: inc ah ;Bump count by one. inc bx ;Point at next char in str. cmp byte ptr es:[bx], 0 jne CoutLoop ; Now AH contains the number of chars in the string. . . .The
getsmroutine also reads a string from the keyboard and returns a pointer to that string in
es:di.The difference between
getsmis that you do not have to pass the address of an input buffer in
Getsmautomatically allocates storage on the heap with a call to
mallocand returns a pointer to the buffer in
es:di. Don't forget that you must call
meminitat the beginning of your program if you use this routine. The SHELL.ASM skeleton file calls
meminitfor you. Also, don't forget to call
freeto return the storage to the heap when you're done with the input line.
getsm ;Returns pointer in ES:DI . . . free ;Return storageto heap.
Putcoutputs a single character to the display device. It outputs the character appearing in the
alregister. It does not affect any registers unless there is an error on output (the carry flag denotes error/no error, as usual). See the Standard Library documentation for more details.
Putcroutputs a "newline" (carriage return/line feed combination) to the standard output. It is completely equivalent to the code:
mov al, cr ;CR and LF are constants putc ; appearing in the consts.a mov al, lf ; header file. putcThe
puts(put a string) routine prints the zero terminated string at which
es:dipoints. Note that
putsdoes not automatically output a newline after printing the string. You must either put the carriage return/line feed characters at the end of the string or call
putsif you want to print a newline after the string.
Putsdoes not affect any registers (unless there is an error). In particular, it does not change the value of the
es:diregisters. The following code sequence uses this fact:
getsm ;Read a string puts ;Print it putcr ;Print a new line free ;Free the memory for string.Since the routines above preserve
es:di(except, of course,
getsm), the call to
freedeallocates the memory allocated by the call to
puthroutine prints the value in the
alregister as exactly two hexadecimal digits, including a leading zero byte if the value is in the range 0..Fh. The following loop reads a sequence of keys from the keyboard and prints their ASCII values until the user presses the Enter key:
KeyLoop: getc cmp al, cr je Done puth putcr jmp KeyLoop Done:The
putiroutine prints the value in
axas a signed 16 bit integer. The following code fragment prints the sum of
Jto the display:
mov ax, I add ax, J puti putcr
Putuis similar to
putiexcept it outputs unsigned integer values rather than signed integers.
putualways output numbers using the minimum number of possible print positions. For example,
putiuses three print positions on the string to print the value 123. Sometimes, you may want to force these output routines to print their values using a fixed number of print positions, padding any extra positions with spaces. The
putusizeroutines provide this capability. These routines expect a numeric value in
axand a field width specification in
cx. They will print the number in a field width of at least
cxpositions. If the value in
cxis larger than the number of print position the value requires, these routines will right justify the number in a field of
cxprint positions. If the value in
cxis less than the number of print positions the value requires, these routines ignore the value in
cxand use however many print positions the number requires.
; The following loop prints out the values of a 3x3 matrix in matrix form: ; On entry, bx points at element [0,0] of a row column matrix. mov dx, 3 ;Repeat for each row. PrtMatrix: mov ax, [bx] ;Get first element in this row. mov cx, 7 ;Use seven print positions. putisize ;Print this value. mov ax, 2[bx] ;Get the second element. putisize ;CX is still seven. mov ax, 4[bx] ;Get the third element. putisize putcr ;Output a new line. add bx, 6 ;Move on to next row. dec dx ;Repeat for each row. jne PrtMatrixThe
print byte "Print this string to the display",cr,lf,0The example above prints the string
"Print this string to the display"followed by a new line. Note that
print byte "This example of the PRINT routine",cr,lf byte "prints several lines of text.",cr,lf byte "Also note,",cr,lf,"that the source lines " byte "do not have to correspond to the output." byte cr,lf byte 0The above displays:
This example of the PRINT routine prints several lines of text. Also note, that the source lines do not have to correspond to the output.It is very important that you not forget about that zero terminating byte. The
Printf, like its "C" namesake, provides formatted output capabilities for the Standard Library package. A typical call to
printfalways takes the following form:
printf byte "format string",0 dword operand1, operand2, ..., operandnThe format string is comparable to the one provided in the "C" programming language. For most characters,
printfsimply prints the characters in the format string up to the terminating zero byte. The two exceptions are characters prefixed by a backslash ("\") and characters prefixed by a percent sign ("%"). Like C's
printf, the Standard Library's
printfuses the backslash as an escape character and the percent sign as a lead-in to a format string.
Printfuses the escape character ("\") to print special characters in a fashion similar to, but not identical to C's
printf. The Standard Library's
printfroutine supports the following special characters:
printfprocesses the format string at run-time. It would see a single "%" and treat it as a format lead-in character. The Standard Library's
printf, on the other hand, processes both the "\" and "%" at run-time, therefore it can distinguish "\%".
printfroutine isn't robust enough to handle sequences of the form "\0xh" which contain only a single hex digit. Keep this in mind if you find printf chopping off characters after you print a value.
Printfgrabs all characters following the call to
printfup to the terminating zero byte (which is why you'd need to use "\0x00" if you want to print the null character, printf will not print such values). The Standard Library's
printfroutine doesn't care how those characters got there. In particular, you are not limited to using a single string after the
printfcall. The following is perfectly legal:
printf byte "This is a string",13,10 byte "This is on a new line",13,10 byte "Print a backspace at the end of this line:" byte 8,13,10,0Your code will run a tiny amount faster if you avoid the use of the escape character sequences. More importantly, the escape character sequences take at least two bytes. You can encode most of them as a single byte by simply embedding the ASCII code for that byte directly into the code stream. Don't forget, you cannot embed a zero byte into the code stream. A zero byte terminates the format string. Instead, use the "\0x00" escape sequence.
printf byte "%i %i",0 dword i,jFormat sequences take the general form "%s\cn^f" where:
printfto print the following value as a 16 bit signed decimal integer. The x and h format characters instruct
printfto print the specified value as a 16 bit or 8-bit hexadecimal value (respectively). If you specify u,
printfprints the value as a 16-bit unsigned decimal integer. Using c tells
printfto print the value as a single character. S tells
printfthat you're supplying the address of a zero-terminated character string,
printfprints that string. The ld, li, lx, and lu entries are long (32-bit) versions of d/i, x, and u. The corresponding address points at a 32-bit value that
printfwill format and print to the standard output.
printf byte "I= %i, U= %u, HexC= %h, HexI= %x, C= %c, " dbyte "S= %s",13,10 byte "L= %ld",13,10,0 dword i,u,c,i,c,s,lThe number of far addresses (specified by operands to the "dd" pseudo-opcode) must match the number of "%" format items in the format string.
Printfcounts the number of "%" format items in the format string and skips over this many far addresses following the format string. If the number of items do not match, the return address for
printfwill be incorrect and the program will probably hang or otherwise malfunction. Likewise (as for the
printfalways prints the values using the minimum number of print positions for each operand. If you want to specify a minimum field width, you can do so using the "n" format option. A format item of the format "%10d" prints a decimal integer using at least ten print positions. Likewise, "%16s" prints a string using at least 16 print positions. If the value to print requires more than the specified number of print positions, printf will use however many are necessary. If the value to print requires fewer, printf will always print the specified number, padding the value with blanks.
Printfwill print the value right justified in the print field (regardless of the data's type). If you want to print the value left justified in the output file, use the "-" format character as a prefix to the field width, e.g.,
printf byte "%-17s",0 dword stringIn this example,
printfprints the string using a 17 character long field with the string left justified in the output field.
printfblank fills the output field if the value to print requires fewer print positions than specified by the format item. The "\c" format item allows you to change the padding character. For example, to print a value, right justified, using "*" as the padding character you would use the format item "%\*10d". To print it left justified you would use the format item "%-\*10d". Note that the "-" must precede the "\*". This is a limitation of the current version of the software. The operands must appear in this order. Normally, the address(es) following the
printfformat string must be far pointers to the actual data to print.
malloc), you may not know (at assembly time) the address of the object you want to print. You may have only a pointer to the data you want to print. The "^" format option tells printf that the far pointer following the format string is the address of a pointer to the data rather than the address of the data itself. This option lets you access the data indirectly.
printfroutine does not support floating point output. Putting floating point into
printfwould increase the size of this routine a tremendous amount. Since most people don't need the floating point output facilities, it doesn't appear here. There is a separate routine,
printff, that includes floating point output.
printfroutine is a complex beast. However, it is very flexible and extremely useful. You should spend the time to master its major functions. You will be using this routine quite a bit in your assembly language programs.
puti, putu, and putlroutines output the numeric strings using the minimum number of print positions necessary. For example,
putiuses three character positions to print the value -12. On occasion, you may need to specify a different field width so you can line up columns of numbers or achieve other formatting tasks. Although you can use
printfto accomplish this goal,
printfhas two major drawbacks - it only prints values in memory (i.e., it cannot print values in registers) and the field width you specify for
printfmust be a constant. The
putlsizeroutines overcome these limitations.
putusize) or the
dx:axregister pair (
putlsize). They also expect a minimum field width in the
cxregister. As with
printf, if the value in the
cxregister is smaller than the number of print positions that the number actually needs to print,
putlsizewill ignore the value in
cxand print the value using the minimum necessary number of print positions.
lsizeroutines do this for you.
isizeroutine expects a signed integer in the
axregister. It returns the minimum field width of that value (including a position for the minus sign, if necessary) in the
Usizecomputes the size of the unsigned integer in
axand returns the minimum field width in the
Lsizecomputes the minimum width of the signed integer in
dx:ax(including a position for the minus sign, if necessary) and returns this width in the
atoi, atoh, atou, itoa, htoa, wtoa,and
utoa(plus others). The
ATOxroutines convert an ASCII string in the appropriate format to a numeric value and leave that value in
ITOxroutines convert the value in
al/axto a string of digits and store this string in the buffer whose address is in
es:di. There are several variations on each routine that handle different cases. The following paragraphs describe each routine.
atoiroutine assumes that
es:dipoints at a string containing integer digits (and, perhaps, a leading minus sign). They convert this string to an integer value and return the integer in
ax. On return,
es:distill points at the beginning of the string. If
es:didoes not point at a string of digits upon entry or if an overflow occurs,
atoireturns the carry flag set.
Atoipreserves the value of the
es:diregister pair. A variant of
atoi2, also converts an ASCII string to an integer except it does not preserve the value in the
atoi2routine is particularly useful if you need to convert a sequence of numbers appearing in the same string. Each call to
diregister pointing at the first character beyond the string of digits. You can easily skip over any spaces, commas, or other delimiter characters until you reach the next number in the string; then you can call
atoi2to convert that string to a number. You can repeat this process for each number on the line.
Atohworks like the
atoiroutine, except it expects the string to contain hexadecimal digits (no leading minus sign). On return,
axcontains the converted 16 bit value and the carry flag denotes error/no error. Like
atohroutine preserves the values in the
es:diregister pair. You can call
atoh2if you want the routine to leave the
diregister pointing at the first character beyond the end of the string of hexadecimal digits.
Atouconverts an ASCII string of decimal digits in the range 0..65535 to an integer value and returns this value in
ax. Except that the minus sign is not allowed, this routine behaves just like
atoi. There is also an
atou2routine that does not preserve the value of the
diregister; it leaves
dipointing at the first character beyond the string of decimal digits.
geturoutines available in the Standard Library, you will have to construct these yourself. The following code demonstrates how to read an integer from the keyboard:
print byte "Enter an integer value:",0 getsm atoi ;Convert string to an integer in AX free ;Return storage allocated by getsm print byte "You entered ",0 puti ;Print value returned by ATOI. putcrThe
wtoaroutines are the logical inverse to the atox routines. They convert numeric values to their integer, unsigned, and hexadecimal string representations. There are several variations of these routines depending upon whether you want them to automatically allocate storage for the string or if you want them to preserve the
Itoaconverts the 16 bit signed integer in
axto a string and stores the characters of this string starting at location
es:di. When you call
itoa, you must ensure that
es:dipoints at a character array large enough to hold the resulting string.
Itoarequires a maximum of seven bytes for the conversion: five numeric digits, a sign, and a zero terminating byte.
Itoapreserves the values in the
es:diregister pair, so upon return
es:dipoints at the beginning of the string produced by
diregister when calling the
itoaroutine. For example, if you want to create a single string containing several converted values, it would be nice if
dipointing at the end of the string rather than at the beginning of the string. The
itoa2routine does this for you; it will leave the
diregister pointing at the zero terminating byte at the end of the string. Consider the following code segment that will produce a string containing the ASCII representations for three integer variables,
; Assume es:di already points at the starting location to store the converted ; integer values mov ax, Int1 itoa2 ;Convert Int1 to a string. ; Okay, output a space between the numbers and bump di so that it points ; at the next available position in the string. mov byte ptr es:[di], ' ' inc di ; Convert the second value. mov ax, Int2 itoa2 mov byte ptr es:[di], ' ' inc di ; Convert the third value. mov ax, Int3 itoa2 ; At this point, di points at the end of the string containing the ; converted values. Hopefully you still know where the start of the ; string is so you can manipulate it!Another variant of the
itoam, does not require you to initialize the
es:diregister pair. This routine calls
mallocto automatically allocate the storage for you. It returns a pointer to the converted string on the heap in the
es:diregister pair. When you are done with the string, you should call
freeto return its storage to the heap.
; The following code fragment converts the integer in AX to a string and prints ; this string. Of course, you could do this same operation with PUTI, but this ; code does demonstrate how to call itoam. itoam ;Convert integer to string. puts ;Print the string. free ;Return storage to the heap.The
utoamroutines work just like
itoam, except they convert the unsigned integer value in
axto a string. Note that
utoa2require, at most, six bytes since they never output a sign character.
wtoamconvert the 16 bit value in
axto a string of exactly four hexadecimal characters plus a zero terminating byte. Otherwise, they behave exactly like
itoam. Note that these routines output leading zeros so the value is always four digits long.
htoamroutines are similar to the
wtoamroutines. However, the
htoxroutines convert the eight bit value in
alto a string of two hexadecimal characters plus a zero terminating byte.
alregister to see if it falls within a certain set of characters. These routines all return the status in the zero flag. If the condition is true, they return the zero flag set (so you can test the condition with a
jeinstruction). If the condition is false, they clear the zero flag (test this condition with
jne). These routines are
alcontains an alphanumeric character.
alto see if it contains a hexadecimal digit character.
alto see if it contains a decimal digit character.
alto see if it contains an alphabetic character.
alto see if it contains a lower case alpha character.
alto see if it contains an upper case alpha character.
alregister. They will convert the character in
alto the appropriate alphabetic case.
alcontains a lower case alphabetic character,
ToUpperwill convert it to the equivalent upper case character. If
alcontains any other character,
ToUpperwill return it unchanged.
alcontains an upper case alphabetic character,
ToLowerwill convert it to the equivalent lower case character. If the value is not an upper case alphabetic character
ToLowerwill return it unchanged.
Randomroutine generates a sequence of pseudo-random numbers. It returns a random value in the
axregister on each call. You can treat this value as a signed or unsigned value since
Randommanipulates all 16 bits of the
idivinstructions to force the output of
randomto a specific range. Just divide the value random returns by some number n and the remainder of this division will be a value in the range 0..n-1. For example, to compute a random number in the range 1..10, you could use code like the following:
random ;Get a random number in range 0..65535. sub dx, dx ;Zero extend to 16 bits. mov bx, 10 ;Want value in the range 1..10. div bx ;Remainder goes to dx! inc dx ;Convert 0..9 to 1..10. ; At this point, a random number in the range 1..10 is in the dx register.The
randomroutine always returns the same sequence of values when a program loads from disk and executes.
Randomuses an internal table of seed values that it stores as part of its code. Since these values are fixed, and always load into memory with the program, the algorithm that
randomuses will always produce the same sequence of values when a program containing it loads from the disk and begins running. This might not seem very "random" but, in fact, this is a nice feature since it is very difficult to test a program that uses truly random values. If a random number generator always produces the same sequence of numbers, any tests you run on that program will be repeatable.
Randomizeuses the current value of the time of day clock to generate a nearly random starting sequence. So if you need a (nearly) unique sequence of random numbers each time your program begins execution, call the
randomizeroutine once before ever calling the
randomroutine. Note that there is little benefit to calling the
randomizeroutine more than once in your program. Once
randomestablishes a random starting point, further calls to
randomizewill not improve the quality (randomness) of the numbers it generates.
NULL = 0 ;Some common ASCII codes BELL = 07 ;Bell character bs = 08 ;Backspace character tab = 09 ;Tab character lf = 0ah ;Line feed character cr = 0dh ;Carriage returnIn addition to the constants above, "stdlib.a" also defines some useful macros including
ExitPgm, lesi, and ldxi. These macros contain the following instructions:
; ExitPgm- Returns control to MS-DOS ExitPgm macro mov ah, 4ch ;DOS terminate program opcode int 21h ;DOS call. endm ; LESI ADRS- ; Loads ES:DI with the address of the specified operand. lesi macro adrs mov di, seg adrs mov es, di mov di, offset adrs endm ; LDXI ADRS- ; Loads DX:SI with the address of the specified operand. ldxi macro adrs mov dx, seg adrs mov si, offset adrs endmThe
ldximacros are especially useful for load addresses into es:di or dx:si before calling various standard library routines (see Chapter Eight for details about macros).