Here are some extra examples of macros. Please refer to the book, or to your class notes, for a description of how macros differ from procedures.
mov_mem macro mem1,mem2 push ax mov ax,mem2 mov mem1,ax pop ax endm
Here are some examples of calling the macro. Assume that num and total have been declared as words.
mov_mem num,total mov_mem num,[bx+4] mov_mem [si],[di]
The names of the parameters are mem1 and mem2. In each example, the arguments from the macro call are substituted for these parameters in the macro body.
push ax mov ax,total mov num,ax pop ax push ax mov ax,[bx+4] mov num,ax pop ax push ax mov ax,[di] mov [si],ax pop ax
It is important to note that the argument [bx+4] cannot be typed with any spaces in it, otherwise the macro will not assemble. If you wanted to include spaces in an argument, then use the < > to indicate that everything inside is the argument.
mov_mem num,<[bx + 4]>
Notice that this macro must always be called with two arguments.
Many times it is necessary to initalize data in a procedure. It is possible to use a macro to minimize the number of statments that need to be written. This macro will initialze three variable/value pairs.
init macro pair1,pair2,pair3 mov pair1 mov pair2 mov pair3 endm
It could be called many ways.
init <ax,0>,<bx,0>,<cx,0> init <ax,cx>,<num,7>,<[bx+2],dx>
Notice that in each call above, there are three parameters, separated by commas. The < > allow commas to be included as part of the argument. These calls would expand as
mov ax,0 mov bx,0 mov cx,0 mov ax,cx mov num,7 mov [bx+2],dx
It is possible to send blank arguments to a macro. For example, it is possible to make a call like
init <ax,0>
Notice that there are 2 commas in the call. That is because the macro definition has three parameters: pair1, pair2, pair3. By only passing one argument, the other two are passed as blank.
There is a problem when this macro is expanded. Enevthough it is possible to make the call, it doesn't mean that the expanded macro will make any sense.
mov ax,0 mov mov
Notice that pair2 and pair3 were blank, so there is nothing after the mov for the last two instructions.
To fix this, it is necessary to use conditional assembly. Conditional assembly uses an if-like structure to determine when to generate machine code. The staement needed here is IFNB, which stands for If Not Blank. If the argument is blank, then the code inside the IFNB .. ENDIF will not be generated.
init macro pair1,pair2,pair3 IFNB <pair1> mov pair1 ENDIF IFNB <pair2> mov pair2 ENDIF IFNB <pair3> mov pair3 ENDIF endm
This macro could be called with 1 non-blank argument, 2 non-blank arguments, or 3 non-blank arguments.
init <ax,0> init <cx,ax>,<num,7> init <ax,9>,<dh,al>,<[bx],bh>
These calls would expand to
mov ax,0 mov cx,ax mov num,7 mov ax,9 mov dh,al mov [bx],bh
Conditional assembly can be used to set default arguments: if an argument is blank, then use a default value. Looking back at mov_mem, it is apparent that only words can be moved, since AX is being used. Here is a version that sets a default size of word, but allows bytes to be moved as well. Notice the addtion of the extra parameter size. The macro uses the conditional assembly statement IFB: If Blank.
mov_mem macro mem1,mem2,size IFB <size> textequ temp,<ax> ELSE textequ temp,<ah> ENDIF push ax mov temp,mem2 mov mem1,temp pop ax endm
It doesn't matter what value the size has. If it is non-blank, then it indicates that a byte move is occuring.
Here's a more complicated example that lets you specify which register to use as a temporary. Pass the letter a,b,c, or d to use ax, bx, cx, or dx. The size is also passed. If the size is not blank, then the ah,bh,ch, or dh are used. The & allows the reg paramter to be expanded into the middle of a string.
mov_mem macro mem1,mem2,size,reg IFB <size> IFB <reg> temp textequ <ax> ELSE temp textequ <reg&x> ENDIF ELSE IFB <reg> temp textequ <ah> ELSE temp textequ <reg&h> ENDIF ENDIF IFB <reg> save textequ <ax> ELSE save textequ <reg&x> ENDIF push save mov temp,mem2 mov mem1,temp pop save endm
This would be called as follows. Assume that num and total are words, and first and last are bytes.
mov_mem num,total,,d mov_mem first,last,byte,c mov_mem num,total
And expanded as
push dx mov dx,total mov num,dx pop dx push cx mov ch,last mov first,ch pop cx push ax mov ax,total mov num,ax pop ax
Notice that the first macro call has a blank parameter. It is necessary to include the blank. Otherwise, the macro would think that d was the size, and not the reg.
This macro implements many of the above ideas. It declares a string that could be part of a menu. The name of the menu is used as the label for the string. Also, there are two assembler variables that kepp track of the maximum width of all strings that are created for this menu, as well as how many strings are in the menu. It uses the conditional assembly statement IFNDEF to determine if the length and width assembler variables should initialized to 0. It also uses the conditional assembly statement IF along with the relational operator GT.
setMenuOpt macro label,num,string IFNDEF label&Width label&Width = 0 label&Length = 0 ENDIF label&Length = label&Length + 1 label&num db string IF ($-label&num) GT label&Width label&Width = ($-label&num) ENDIF endm
It could be called as follows
setMenuOpt main,1,<"1: (I)nput a list of numbers",0dh,0ah,"$"> setMenuOpt main,2,<"2: (D)isplay the numbers that were entered",0dh,0ah,"$"> setMenuOpt main,3,<"3: (F)ind the average of the numbers",0dh,0ah,"$"> setMenuOpt main,4,<"4: (P)rint the even numbers above the average",0dh,0ah,"$"> setMenuOpt main,5,<"5: (E)xit the program",0dh,0ah,"$"> setMenuOpt main,6,<0dh,0ah,"$"> setMenuOpt main,7,<"Please make a selection (1-5): ","$">
This would generate the following and would create two variables mainWidth with the value 48 and mainLength with the value 7
main1 db "1: (I)nput a list of numbers",0dh,0ah,"$" main2 db "2: (D)isplay the numbers that were entered",0dh,0ah,"$" main3 db "3: (F)ind the average of the numbers",0dh,0ah,"$" main4 db "4: (P)rint the even numbers above the average",0dh,0ah,"$" main5 db "5: (E)xit the program",0dh,0ah,"$" main6 db "0dh,0ah,"$" main7 db "Please make a selection (1-5): ","$"
Here is a macro that generates a for loop. It can only have one statement, but that could be a call to a procedure. It usees the local directive to force the assembler to generate new, unique labels for top and done each time the macro is called. Without the local directive, the macro would generate duplicate labels if it was called more than once inside the same procedure.
forloop macro start,stop,incr,statement local top,done push ax mov ax,start top: cmp ax,stop jge done statement add ax,incr jmp top done: pop ax endm
This could be called as
xor bx,bx forloop 5,10,3,<add bx,ax>
It would generate the following. It would accumulate the loop control variable ax.
push ax mov ax,5 top1: cmp ax,10 jge done1 statement add ax,3 jmp top1 done1: pop ax