Hi! Welcome to the eleventh chapter of this series. I hope that you really understood the subroutine concept we discussed in the last chapter. Now, we're going to learn about another way to do code modularization: by grouping them into macros. C/C++ folks probably familiar with macros but others may have no clue. In explaining macro, I don't really detail the definition of macro, but rather to have you examine it and use it in the program. Then, I will wrap up all of these in the conclusion. For now, to simplify matters, think that the macro is just like subroutine, of course it has some differences. You will understand after I explain the whole stuff. Note that I will tightly link this discussion with what we have understood from the last chapter. So, probably you'd like to go back and forth.
How can we define a macro? It's pretty much like defining subroutine. Recall our revised printout subroutine discussed in the last chapter
TASM | MASM |
---|---|
macro printout msgptr push dx push ax mov dx, msgptr mov ah, 9 int 21h pop ax pop dx endm | printout macro msgptr push dx push ax mov dx, msgptr mov ah, 9 int 21h pop ax pop dx printout endm |
Hmm, pretty similar isn't it? You'll quickly notice some differences:
Uhuh... so, how can we call the macro then? It's simple:
: : printout offset message :
So, there is no call or invoke keyword too. The rules in subroutines you know so far also apply here in macros, except that the usual place to declare macros is just before the codeseg keyword (instead of the area 1 and 2).
OK, now the real difference between macros and subroutines. Whenever the assembler (not the processor) encounters macro invocation while assembling the program, it replaces that invocation with the definition. It also replaces the arguments appropriately. So, suppose we have this main program:
: : codeseg msg1 db 'Hello World!$' msg2 db 'Hi there!$' msg3 db 'Selamat Datang!$' start: printout offset msg1 ;---------------------- printout offset msg2 ;---------------------- printout offset msg3 mov ax, 4c00h int 21h :
This program will actually converted to this first:
: : codeseg msg1 db 'Hello World!$' msg2 db 'Hi there!$' msg3 db 'Selamat Datang!$' start: push dx push ax mov dx, offset msg1 mov ah, 9 int 21h pop ax pop dx ;---------------------- push dx push ax mov dx, offset msg2 mov ah, 9 int 21h pop ax pop dx ;---------------------- push dx push ax mov dx, offset msg3 mov ah, 9 int 21h pop ax pop dx mov ax, 4c00h int 21h :
Ah, so the macros just behave like a broiler-plate of codes. Notice that the parameter msgptr get replaced appropriately. The assembler just stamp the codes out and replaces whenever it encounter a match. Because of this behavior, we (usually) don't need to have ret in macros. Of course we don't want to make our program quit early, right? The subroutines doesn't behave like this. Instead it keeps one copy of the subroutine intact. Everytime there is an invocation, an actuall call instruction is inserted appropriately.
Notice also that the assembly do the replacement "dumb-foundedly". It means, prior to the replacement it doesn't check the types (whether legal or not) and other stuffs. Rather, it checked the validity of your program after it gets expanded. This behavior can create a lot of headaches for programmers because: For one particular macros, there are cases that the result of replacement are legal, some aren't depending on how you invoke macros or how the parameters get passed. You probably don't realize this at this moment, but as you go along, you will notice this too. I don't want to explain this now as I think it would be premature. One rule of thumb for building macros is "program defensively" think all possibilities that it may go wrong.
Note that: The source code of expanded program is not dumped out or some sort, it exist only in your mind or in the debugger. So, employing macros may hamper you in debugging because you don't know what is actually replaced and the correspondence between the executable code with your .asm files may blur.
However, there is a significant advantage too. Since macros use string-based subtitution, you can rearrange your program to benefit from this approach. Notice that the expanded version of the program employs unnecessary pushes and pops. Since you know the context of all printout macro invocation in the program, you know that these can be thrown out. Thus, our macro can be slimmed down by four bytes -- trimming out pushes and pops. This way, you can optimize a bit more than you can do in subroutines. Warning: Do this with great caution since the elimination may make the macros unsafe. But of course, in this example, it's perfectly safe.
Pay more attention if your macro uses labels. It is likely that the labels will conflict each others. Why? I leave this for you to answer. :-)
Since macro do not need call and ret, macro is considerably faster than subroutines because the calling and returning time penalty is usually high (in more advanced processors, it may involve trashing caches and so on).
OK, so the main differences (behavior-wise) are:
OK, now.... So when we should use macros or subroutines? The rule of thumb is that: You should generally use subroutines. Macros are ideal to specify small helper routines which tend to be oftenly used, just like printout example above, which involves less than 10 instructions. These kind of macros are easy to make and will speed up your overall code. For larger ones, just go for subroutines.
OK, I think that's all for now. See you next time.
Chapter 12
News Page
x86 Assembly Lesson 1 index
Contacting Me
Roby Joehanes © 2001