Macros

Another Way For Modularization




Welcome

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.

 

Macro Syntax

How can we define a macro? It's pretty much like defining subroutine. Recall our revised printout subroutine discussed in the last chapter

 
TASMMASM
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).

 

Under the Hood

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).

 

Recap

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.

 

Closing

OK, I think that's all for now. See you next time.

 


Where to go

Chapter 12
News Page
x86 Assembly Lesson 1 index
Contacting Me


Roby Joehanes © 2001