Chapter 10

Structures and Macros

Modified
© Ray Wisman

Overview

The importance of algorithm abstraction and modularity is such that virtually all high-level languages realize these notions, generally as procedures and functions with parameters. Translation from C++ algorithms to Assembly can be made once a few basic rules for passing parameters are established. Though there are numerous methods to pass data to a procedure, the simplest and the one discussed here is the use of global variables and register parameters to interface the calling and the called functions. The more general and useful method of passing parameters via the stack will also be discussed later as this method is most often used by high level languages.

Macros are another method of implementing modularity and abstraction in Assembler (and C, spreadsheets, and many languages). Though both are abstraction techniques, macros are very different from a function in use.

10.1 Struc - Defining Data Structures

C++ provides user defined data types and structures. The struc statement defines the data structure fields and the '.' operator provides access to the structure fields. Assembler provides very similar structure definitions and access operations.

In the following, a person structure is defined, 5 person variables are created and initialized.

The firstname and age field of one of the structures is printed. It is instructive to examine the memory layout of a structure.

person struc                       
       firstname byte     6 dup (?)
                 byte     0
       age       dword    ?
       next      dword    NIL
person ends

The P1 person structure is in memory below:

P1.firstname is offset from the start of P1 by 0 bytes.

P1.age is offset from the start of P1 by 7 bytes.

P1.next is offset by 10 (or A16) bytes (0000003816 is the presumed address of P2).

  P1  person      {"Mary  ", 0, 19, P2} 
                 
firstname   age next
M a r y     0 13 00 00 00 38 00 00 00
0 1 2 3 4 5 6 7 8 9 A B C D E

The following prints the firstname and age of the P1 structure, Mary and 19.

#include <iostream.h>

struct person {
	char   *firstname;
	int    age;
	person *next;
};

// Mary->John->Elmer->Sally->Harry->NIL
 person P5 = {"Harry ",22,NULL};
 person P4 = {"Sally ",24,&P5};
 person P3 = {"Elmer ",20,&P4};
 person P2 = {"John  ",18,&P3};
 person P1 = {"Mary  ",19,&P2};

void main(void) {
   cout << P1.firstname << "\n";
   cout << P1.age;
}
INCLUDE Irvine32.inc

NIL         equ      -1          

person 	struc                        
       	firstname byte     6 dup (?)
       	          byte     0
       	age       dword    ?
       	next      dword    NIL	
person 	ends	

.data   
  P5  person      {"Harry ",0,22,NIL}
  P4  person      {"Sally ",0,24,P5}
  P3  person      {"Elmer ",0,20,P4} 
  P2  person      {"John  ",0,18,P3}
  P1  person      {"Mary  ",0,19,P2}  

.code
main 	Proc		near 	
	Lea		eDx, P1.firstname
	Call		WriteString	
	Call		CrLf	
	Mov		eAx, P1.age
	Call		WriteDec

	invoke	ExitProcess, 0
main	Endp
      End		main

1.    Question - Assuming:

 P1  person {"Mary  ",0,19,P2}

What is eDx after: Mov eDx, P1.next

 

Linked Lists

The next field of each person is a reference to another person constructing a linked list from

P1 -> P2 -> P3 -> P4 -> P5

The list person first name field is printed by iterating through each person until a NIL is reached.



Offset

00000047  
  P1  person      {"Mary  ", 0, 19, P2} 
                 
firstname   age next
M a r y     0 13 00 00 00 38 00 00 00
0 1 2 3 4 5 6 7 8 9 A B C D E

 



Offset

00000038  
  P2  person      {"John  ", 0, 18, P3} 
                 
firstname   age next
J o h n     0 12 00 00 00 29 00 00 00
0 1 2 3 4 5 6 7 8 9 A B C D E
#include <iostream.h>

struct person {
	char   *firstname;
	int    age;
	person *next;
};

// Mary->John->Elmer->Sally->Harry->NIL
 person P5 = {"Harry ",22,NIL};
 person P4 = {"Sally ",24,&P5};
 person P3 = {"Elmer ",20,&P4}; 
 person P2 = {"John  ",18,&P3};
 person P1 = {"Mary  ",19,&P2};

void main(void) {

 person *eBx = &P1;
	
 while( eBx != NIL) {
   cout << eBx->firstname << "\n";
   eBx = eBx->next;
 }
}
INCLUDE Irvine32.inc

NIL         equ      -1          

person 	struc                        
       	firstname byte     6 dup (?)
       	          byte     0
       	age       dword    ?
       	next      dword    NIL	
person 	ends	

.data   
  P5  person      {"Harry ",0,22,NIL}
  P4  person      {"Sally ",0,24,P5}
  P3  person      {"Elmer ",0,20,P4} 
  P2  person      {"John  ",0,18,P3}
  P1  person      {"Mary  ",0,19,P2}  

.code
main 	Proc		near 
	Mov		eBx, offset P1		
  .WHILE		eBx != NIL 				
	Lea		eDx, [eBx].person.firstname			
	Call		WriteString	
	Call		CrLf	
	Mov		eBx, [eBx].person.next	
  .ENDW				

	invoke	ExitProcess, 0
main	Endp
      End     	main

2.    Question - Assuming:

Mov eBx, offset P3

What is:

[eBx].person.firstname

Given that P2 is stored in memory as below; give the precise storage representation of P3:



Offset

00000038  
  P2  person      {"John  ", 0, 18, P3} 
                 
firstname   age next
J o h n     0 12 00 00 00 29 00 00 00
0 1 2 3 4 5 6 7 8 9 A B C D E


Offset
                 
   
  P3  person      {"Elmer ",0,20,P4} 
                 
firstname   age next
                                                          
0 1 2 3 4 5 6 7 8 9 A B C D E

 

Recursion on Data Structures

The same linked list structure is used from above but recursion replaces iteration as the means of traversing the list.

Note that in C++ and Assembler, the address of a person variable is passed to the Print function, that is call-by-reference.

#include <iostream.h>

struct person {
	char   *firstname;
	int    age;
	person *next;
};

// Mary->John->Elmer->Sally->Harry->NIL
  person P5 = {"Harry ",22,NULL};
  person P4 = {"Sally ",24,&P5};
  person P3 = {"Elmer ",20,&P4}; 
  person P2 = {"John  ",18,&P3};
  person P1 = {"Mary  ",19,&P2}; 


void Print(person *P) {
	person *eBx=P;

	if(eBx != NIL) {
	  cout << eBx->firstname << "\n";
	  Print(eBx->next);
	}
}

void main(void) {

  Print(&P1);
}
Output
Mary
John
Elmer
Sally
Harry
INCLUDE Irvine32.inc

NIL         equ      -1          

person 	struc                        
       	firstname byte     6 dup (?)
       	          byte     0
       	age       dword    ?
       	next      dword    NIL	
person 	ends	

.data   
  P5  person      {"Harry ",0,22,NIL}
  P4  person      {"Sally ",0,24,P5}
  P3  person      {"Elmer ",0,20,P4} 
  P2  person      {"John  ",0,18,P3}
  P1  person      {"Mary  ",0,19,P2}  

.code
Print   Proc    near, P : near ptr person 
        Mov     eBx, P

 .IF    eBx != NIL       
        Lea     eDx, [eBx].person.firstname
        Call    WriteString
        Call    CrLf

        invoke  Print, [eBx].person.next
 .ENDIF
	  Ret
Print   Endp

main 	Proc		near 
	invoke	Print, addr P1

	invoke	ExitProcess, 0
main	Endp
	End     	main

3.    Question

Which record follows P2:

in the linked list?

in memory?

The names cannot be printed in the opposite order. Why not?

 

10.2    Macros

Macros allow a programmer to define pseudo operations, typically operations that are of general use, but not implemented as part of the processor instruction, and can be implemented as a sequence of regular assembler instructions.

It is important to note one distinction between functions and macros:

Calling a procedure multiple times executes the same instructions each time.

Each use of a macro generates new program instructions, the macro use has the effect of automating the writing of the program.

Consider a typical scenario where one needs to do a number of different divisions of the eAx register by the divisor 10.

The following lists the typical evolution of macro development and usage. The final result is the expansion of the macro in which the generated assembler code becomes part of the program. In the following example the macro use simply inserts the three instructions of the macro definition.

Typical evolution implementing a Macro
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Div  10



Mov  eDx, 0
Mov  eBx, 10  
Div  eBx

_Div10  Macro
        Mov    eDx, 0
        Mov    eBx, 10    
        Div    eBx
endm
Call  ReadDec
_Div10
Call  WriteDec   

  
Call  ReadDec
  Mov   eDx, 0
  Mov   eBx, 10
  Div   eBx
Call  WriteDec
Parameters

Macro parameters support code reuse, allowing one macro definition to generate or implement multiple, similar algorithms. In the following, the _DIV macro has a single parameter N. When the macro is used in the program, the actual parameter used, 34, is substituted for the formal parameter, N, defined in the macro prototype during the macro expansion. Now the same macro definition when expanded, produces code to divide by any unsigned integer parameter.

Macro Parameters
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Div  N



Mov  eDx, 0
Mov  eBx, N  
Div  eBx

_Div    Macro  N
        Mov    eDx, 0
        Mov    eBx, &N    
        Div    eBx
endm
Call  ReadDec
_Div  34
Call  WriteDec   

  
Call  ReadDec
  Mov   eDx, 0
  Mov   eBx, 34
  Div   eBx
Call  WriteDec

4.    Question

Write a macro to print a string given an identifier, for example:

HelloWorld    byte        "Hello World", 0

_Print    HelloWorld

would expand to:

Mov        eDx, offset HelloWorld
Call         WriteString

5.    Question

What is the expansion of:

_Div        27

 

Labels

Labels must be unique. When used in macros, multiple expansions of a macro can produce multiple definitions of the same label. To see the problem consider the macro below to produce the absolute value of a signed integer variable. For one use of the _Abs macro, the if:, then:, and endif: label definitions are unique, two or more uses of the _Abs macro leads to multiple definitions of the if:, then:, endif: labels.


Local Symbols

The solution to the label problem is to generate unique symbol(s) each time the macro is used.

The solution is to implement a counter that is incremented each time a symbol defined to be local is encountered during macro expansion. The following illustrates the use of local symbols to produce unique symbols in place of if, then, and endif. The first table is correct but produces labels that bear little connection to the original structure. The second produces more readable code.

Macro Label Solution
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Abs   X







if: Cmp X, 0 
    Jl  then
    Jmp endif
then:
    Neg X
endif:


_Abs  Macro   X
  local if, then, endif
  if: Cmp &X, 0 
      Jl  then
      Jmp endif
  then:
      Neg &X
  endif:
endm
Call  ReadDec
_Abs  Z
Call  WriteDec   

  



       Call  ReadDec
$0001: Cmp   Z, 0 
       Jl    $0002
       Jmp   $0003
$0002:
       Neg   Z
$0003:
       Call  WriteDec
Macro Label Solution
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Abs   X






if: Cmp X, 0 
    Jl  then
    Jmp endif
then:
    Neg X
endif:

_Abs  Macro   X
  local label
  if&label: Cmp &X, 0 
      Jl  then
      Jmp endif
  then&label:
      Neg &X
  endif&label:
endm
Call  ReadDec
_Abs  Z
Call  WriteDec   

  


         Call ReadDec
if$0001: Cmp  Z, 0 
         Jl   then$0001
         Jmp  endif$0001
then$0001:
         Neg  Z
endif$0001:
         Call WriteDec
 

6.    Question

Assuming _Abs had been called 2 times, what is the expansion of:

_Abs    Pivot

 

10.3    Conditional Assembly Directives

Means that some sections of the program may be optional, either included or not in the final program, dependent upon the specified conditions. A reasonable use of conditional assembly would be to combine two versions of a program, one that prints debugging information during test executions for the developer, another version for production operation that displays only results of interest for the average user. A program fragment that assembles the instructions to print the Ax register only if Debug is true is given below. Note that true is any non-zero value so that Debug equ 1 makes Debug true or non-zero.
 

Generate code to display Ax when Debug is non-zero.
Generate no code when Debug is zero.
Debug  equ  1         ; Debug is true
      :
      :
     Mul   eBx
     If    Debug    ; Assemble when Debug is true
       Call    WriteDec
     Endif
     Sub   eCx, 12
Debug  equ  0       ; Debug is false
      :
      :
     Mul   eBx
     If    Debug    ; Assemble when Debug is true



     Endif
     Sub   eCx, 12

Macro Messages

Errors can be signaled to the console during assembly using:

Testing for Blank Macro Parameters/Exit Expansion

It is possible to perform some simple tests for correctness of macro parameters, the simplest is to verify that all parameters are defined. Consider a macro _Max that returns the maximum of two unsigned integers in the Ax register. If either of the two values are missing the macro should display an error message to the screen but not expand to produce any program instructions.

Testing for Blank Parameters
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Max   X, Y 
    Mov eAx, X
if: Cmp eAx, Y  
    Ja  then
    Jmp else
then:
    Mov eAx, X
    Jmp endif
else:
    Mov eAx, Y
endif:
_Max    Macro  X, Y  
        local L
ifB    <&Y> 
        %outm Error 
        exitm
endif
        Mov eAx, &X
  if&L: Cmp eAx, &Y 
        Ja  then&L
        Jmp else&L
  then&L:
        Mov eAx, &X
        Jmp endif&L
  else&L:
        Mov eAx, &Y
  endif&L:
endm
Call  ReadDec
_Max  Z, 5
Call  WriteDec  
_Max  Z   








  



       Call  ReadDec
       _Max     Z, 5
       Mov eAx, Z
if$01: Cmp eAx, 5    
       Ja  then$01
       Jmp else$01
then&01:
       Mov eAx, Z
       Jmp endif$01
else$01:
       Mov eAx, 5
endif$01:
       Call  WriteDec

       _Max  Z
No expansion due to
missing parameter.

7.    Question

What is the expansion of:

_Max
 

Examples
Euclid's Greatest Common Divisor Macro
Modulus operator
Result parameter must be a
variable or register other
than eAx, eBx, or eDx
Interchange operator 
All parameters must be variables 
or registers other than eAx, eBx
GCD operator
All parameters must be variables or
registers other than eAx, eBx, eDx
_Mod   Macro   N, Divisor, Result
       PushA   
       Mov     eAx, &N
       Mov     eBx, &Divisor
       Mov     eDx, 0
       Div     eBx
       Mov     &Result, Dx
       PopA
endm


_Swap Macro  A, B
      PushA  
      Mov    eAx, &A
      Mov    eBx, &B
      Mov    &A,  eBx
      Mov    &B,  eAx
      PopA
endm
             


_Gcd Macro  X, Y
       local  L
  while&L:
       cmp    &Y, 0
       Jne    do&L
       Jmp    endwhile&L
   do&L:
       _Mod   &X, &Y, &X
       _Swap  &X, &Y
       Jmp    while&L
  endwhile&L:
endm

 
Expansion of _GCD, _MOD, and _SWAP
  _GCD A, B

   while$0001:
       cmp    B, 0
       Jne    do$0001
       Jmp    endwhile$0001 
   do$0001:
   ; _Mod   A, B, A
         PushA   
         Mov     eAx, A
         Mov     eBx, B
         Mov     eDx, 0
         Div     eBx
         Mov     A, eDx
         PopA
   ; _Swap  A, B
         PushA  
         Mov    eAx, A
         Mov    eBx, B
         Mov    A, eBx
         Mov    B, eAx
         PopA
       Jmp    while$0001
  endwhile$0001:

_OR

Performs a logical OR operation on the results of two relations. The Carry flag is set equal 1 if the result is true and the Carry flag is reset equal 0 if the result is false. For example:

        Mov  N, 12                     ;; N = 12;
Do:                                    ;; Do
        Dec   N                        ;;        N = N - 1;
While:  _OR  N, Jl, 0, N, Jg, 10       ;; While (N < 0) || (N > 10)
        Jc    Do                              
EndDo:
would set the Carry flag equal 1 when 0 <= N <= 10. To set Carry flag equal 1 use STC instruction, reset Carry flag equal 0 use CLC instruction. The following macro will test that there are six actual parameters provided to the macro, if any parameters are missing the macro will exit without generating any code.
Definition and Expansion of _OR macro
Definition
_OR     Macro        Opr1, Condition1, Opr2, Opr3, Condition2, Opr4
        Local        L
     IfB             <&Opr4>    
        %Outm        "Missing operand in OR"
        exitm
     Endif

     if&L:
        Cmp          &Opr1, &Opr2
        &Condition1  then&L
     or&L:
        Cmp          &Opr3, &Opr4
        &Condition2  then&L
        Jmp          else&L
     then&L:
        Stc
        Jmp          endif&L
     else&L:
        Clc
     endif&L:
Endm
Expansion
_OR     N, Jl, 0, N, Jg, 10

    if$0001:
        Cmp          N, 0
        Jl           then$0001
     or$0001:
        Cmp          N, 10
        Jg           then$0001
        Jmp          else$0001
     then$0001:
        Stc
        Jmp          endif$0001
     else$0001:
        Clc
     endif$0001:
 


Problems - Capture

Macro parameters can be captured when the macro is expanded.

Interchange operator 
All parameters must be variables 
or registers other than eAx, eBx
_Swap Macro  A, B
      PushA  
      Mov    eAx, &A
      Mov    eBx, &B
      Mov    &A,  eBx
      Mov    &B,  eAx
      PopA
endm

8.    Question

What is the expansion of:

_Swap    eAx, eBx

 

Program Organization

Macro definitions must be placed at the beginning of a program prior to any other executable statements.
 

Include Irvine32.inc
_Abs  Macro   X
        local if, then, endif
    if: Cmp &X, 0 
        Jl  then
        Jmp endif
    then:
        Neg &X
    endif:
endm
.code
main    Proc    Near        ; void main()  {

        Call    ReadDec     ;       cin >> eAx ;         
       _Abs    eAx            
        Call    WriteDec    ;       cout << eAx ;   

        Push    0            
        Call    ExitProcess    
main    Endp
        End     main