Chapter 10Structures and Macros |
Modified: |
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.
- A function exists as a single copy of an algorithm, the function parameters vary the results produced by the function execution.
- A macro exists as a template of a generic algorithm, the macro parameters vary the program instructions generated.
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 endsThe 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
00000047P1 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
00000038P2 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
00000038P2 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
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?
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.
| 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 |
| Macro-name MACRO
<formal parameters>
<macro body> ENDM |
| Macro-name <actual-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.
| 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 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.
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.
| 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 |
| 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.
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:
- %out message
- echo message
- .err message
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.
| 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
| 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 |
_GCD A, B while$0001: cmp B, 0 Jne do$0001 Jmp endwhile$0001 do$0001:
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
_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
.code
main Proc Near ; void main() {
Call ReadDec ; cin >> eAx ;
Call WriteDec ; cout << eAx ;
Push 0
Call ExitProcess
main Endp
End main
|