Macros vs Procedures

Expansion vs Call/Ret.

Darshan N
Updated: 19 March 2026
8 min read

In 8086 assembly language programming, both macros and procedures allow a programmer to write reusable code blocks. However, the mechanism by which each works at the machine level is fundamentally different. A macro is a text substitution performed by the assembler before code generation, while a procedure is a separate code block reached through a CALL instruction at runtime. Understanding this distinction is critical for program design, memory efficiency, and examination accuracy.

Macros vs Procedures (8086)MACROAssembler expands inlineCode duplicatedUsed 3 times → 3 copiesLarger memory usageNo CALL / RETFaster executionCompile-time substitutionPROCEDURESingle shared code blockOne copy in memoryMultiple CALL instructionsSmaller program sizeCALL / RET usedSlight runtime overheadRuntime control transfer
Figure 1: Side-by-side comparison of macro expansion and procedure call mechanisms in 8086 assembly

Core Concept Explanation

A macro in 8086 assembly is defined using the MACRO and ENDM directives. When the assembler encounters a macro invocation in the source code, it replaces the invocation with a copy of the macro body. This is called macro expansion and it happens entirely during the assembly phase, before the executable code is generated. The expanded code becomes part of the surrounding program, as if the programmer had typed it manually at each point of use. If the macro is used five times, the macro body is physically copied five times into the object code.

A procedure in 8086 assembly is defined using the PROC and ENDP directives with a label. The procedure body exists as a single copy at one memory location. When the program needs to execute the procedure, it uses a CALL instruction that pushes the return address onto the stack and transfers control to the procedure. When the procedure finishes, the RET instruction pops the return address from the stack and resumes execution after the CALL. A near CALL pushes only IP (2 bytes), while a far CALL pushes CS and IP (4 bytes).

The critical difference is: macros increase code size (one copy per use) but have no runtime overhead, while procedures keep a single code copy (smaller size) but incur the overhead of CALL, stack push, RET, and stack pop for each invocation. For small frequently-used code sequences where speed is more important than memory, macros are preferred. For larger or less frequently called routines, procedures are more efficient.

Macro Parameters and Advanced Use

Macros can accept formal parameters that are substituted with actual values at each expansion point. For example, a macro defined as PRINT MACRO MSG replaces MSG in the body with whatever argument is provided when the macro is invoked. This makes macros more flexible than simple copy-paste. Unlike procedure parameters, which are passed through registers or the stack at runtime, macro parameters are resolved by the assembler at assembly time with direct text substitution.

The LOCAL directive is used inside macros to define label names that are unique to each expansion. Without LOCAL, if a macro containing a label is expanded twice, the assembler reports a duplicate label error. LOCAL tells the assembler to generate unique labels (like ??0001, ??0002) for each expansion automatically.

Mathematical Expression

If a macro body consists of N bytes of machine code and it is invoked M times, the total code size contribution is N x M bytes. In contrast, a procedure of the same N bytes used M times contributes N + M x 5 bytes (N for the procedure body, plus 5 bytes per CALL instruction for near calls in typical 8086 encoding which is 3 bytes CALL + 2 bytes RET overhead factoring stack management). For the procedure to be smaller, N x M must be greater than N + M x 5, which simplifies to N x (M-1) greater than M x 5, or N greater than 5 x M divided by (M-1). For M=5, this means N must be greater than 6.25, so any procedure body of 7 bytes or more saves memory when called 5 times or more.

Practical Understanding

In assembly language labs, macros are commonly used for short frequently repeated sequences such as saving and restoring registers (PUSHALL and POPALL equivalent macros), displaying a newline (CR+LF sequence), or clearing the screen. These are short sequences where the overhead of a CALL/RET is comparable to or larger than the code itself, making macros the better choice.

Procedures are the preferred choice for modular program design. Larger functional blocks such as input validation routines, number conversion routines, or sorting algorithms are best written as procedures. They allow separate testing, easier debugging, and reduced memory footprint. In real programs, procedures also interact with the stack for parameter passing, making them essential for understanding function call conventions in both assembly and high-level languages like C.

Solved Numerical Example

A programmer has a code block of 20 bytes that needs to be used 8 times in a program. Compare the total code size if implemented as a macro versus a procedure, assuming each CALL instruction is 3 bytes and RET is 1 byte.

Example
Given:
Code block size N = 20 bytes
Number of uses M = 8
Near CALL size = 3 bytes
RET size = 1 byte

Why this formula applies:
Macro: N bytes copied for each use
Procedure: N bytes once + (CALL + RET) for each call

Formula:
Macro total = N x M
Procedure total = N + M x (CALL size + RET size)

Substitution:
Macro total = 20 x 8 = 160 bytes
Procedure overhead per call = 3 (CALL) + 1 (RET) = 4 bytes
Procedure total = 20 + 8 x 4

Calculation:
Procedure total = 20 + 32 = 52 bytes

Final Answer:
Macro code size = 160 bytes
Procedure code size = 52 bytes
Memory saving using procedure = 160 - 52 = 108 bytes
Procedure reduces code size by 67.5% in this case.
Exam Tip: Macros cause code expansion (inline copy at each use), so they increase executable size. Procedures use CALL/RET with stack push and pop, so they have runtime overhead but smaller code. A near CALL pushes only IP (2 bytes); a far CALL pushes CS and IP (4 bytes). This is a frequent exam MCQ topic.
CALL/RET Stack Mechanism vs Macro Inline ExpansionProcedure CALL / RET FlowMain: ... CALL PROC_A ...Push IP (return addr) to stackJump to PROC_A codeExecute procedure bodyRET: Pop IP from stackResume after CALL in mainMacro Inline ExpansionSource: MYMACRO (use 1)Expanded: MOV AH,09h INT 21h (copy 1)Source: MYMACRO (use 2)Expanded: MOV AH,09h INT 21h (copy 2)Source: MYMACRO (use 3)Expanded: MOV AH,09h INT 21h (copy 3)No stack use. No jump. Inline.
Figure 2: CALL/RET stack mechanism for procedures versus inline code expansion for macros in 8086 assembly
  • Macros expand inline at each use; procedures exist as a single code copy at a fixed address.
  • Macro expansion is assembler-time; CALL/RET is runtime with stack operations.
  • Near CALL pushes IP (2 bytes); far CALL pushes CS + IP (4 bytes) onto the stack.
  • Macro code size = N x M; procedure code size = N + (4 x M) for typical near calls.
  • LOCAL directive in macros avoids duplicate label errors across multiple expansions.
  • Macro parameters are text-substituted at assembly time; procedure parameters pass at runtime via registers or stack.

Quick Revision

  • Macro: assembler-time expansion, no CALL/RET, one code copy per use, faster but larger code.
  • Procedure: runtime CALL/RET, single code copy, saves memory for large/frequently called blocks.
  • Macro code size = N x M bytes. Procedure code size = N + M x (CALL+RET bytes).
  • Near CALL: IP pushed (2 bytes). Far CALL: CS+IP pushed (4 bytes). Near RET: IP popped.
  • Use LOCAL in macros to avoid duplicate label errors across multiple expansions.
  • Exam trap: macros do NOT use the stack at runtime; procedures always use the stack for return address.
  • Prefer macros for short speed-critical code; prefer procedures for modular, memory-efficient programs.

Macros And Procedures

Compare code reuse mechanisms in assembly.

Question 1 of 3

Q1.What is the primary architectural disadvantage of using macros extensively?