CSC365 Compiler Design and Construction

Compiler Design and ConstructionUnit 128 min read

Macros, Preprocessing & Compiler Passes

Unit 12 of Compiler Design and Construction covers macro definitions, preprocessing directives, macro expansion rules, and the distinction between one-pass vs. multi-pass compilers, with real-world examples from C/C++ and practical traces of macro substitutions.

TAKEAWAYS:

  • Macros are text substitutions performed before compilation, unlike functions which execute at runtime.
  • The preprocessor handles #define, #include, and conditional directives (#ifdef) before the compiler processes code.
  • One-pass compilers process input in a single scan (e.g., early C compilers), while multi-pass compilers (e.g., GCC) separate lexical/syntax analysis from code generation.
  • Macro expansion can cause side effects (e.g., SQUARE(2+3) evaluates to (2+3)*(2+3)) and recursion issues (e.g., #define CUB(x) x*x*x).
  • Advantages: Macros reduce code duplication; disadvantages: They obscure logic and can introduce bugs.
  • Exam focus: Trace macro expansion step-by-step and compare one-pass vs. multi-pass compilers.

1. What Are Macros?

Macros are preprocessor directives that perform text substitution before compilation. Unlike functions, macros do not have a separate call stack or type safety. They are defined using #define and expanded by the preprocessor.

Macro Definition Syntax

#define MACRO_NAME(replacement_list) replacement_text

Example:

#define PI 3.14159
#define SQUARE(x) ((x) * (x))

Macro Expansion Process

The preprocessor replaces every occurrence of MACRO_NAME with replacement_text before the compiler sees the code.

Example Expansion:

printf("Area = %f", PI * SQUARE(5));  // After expansion:
printf("Area = %f", 3.14159 * ((5) * (5)));

2. Types of Macros

Type Syntax Example Key Feature
Object-like Macro #define NAME replacement #define PI 3.14159 Simple text substitution.
Function-like Macro #define NAME(args) replacement #define SQUARE(x) ((x)*(x)) Takes arguments; expands like a function.
Conditional Macro #ifdef, #ifndef, #else #ifdef DEBUG Enables/disables code blocks.
Parameterized Macro #define NAME(args) ... #define MAX(a,b) ((a)>(b)?(a):(b)) Evaluates arguments before expansion.

3. Macro Expansion with Examples

Example 1: Object-like Macro

#define GREET "Hello, World!"
printf(GREET);  // Expands to: printf("Hello, World!");

Example 2: Function-like Macro (with Pitfalls)

#define SQUARE(x) ((x) * (x))
int y = SQUARE(2+3);  // Expands to: ((2+3) * (2+3)) → 25 (not 16!)

Why? Macros expand before evaluation. Parentheses prevent incorrect grouping.

Example 3: Conditional Macro (Debugging)

#define DEBUG
int x = 10;
#ifdef DEBUG
    printf("Debug: x = %d\n", x);  // Only compiled if DEBUG is defined.
#endif

4. Macro vs. Function

Feature Macro Function
Execution Time Preprocessing (compile-time) Runtime
Type Safety No (text substitution) Yes (type-checked)
Overhead None (text replacement) Function call overhead
Argument Evaluation Evaluated before expansion Evaluated at call time
Recursion Can cause infinite expansion Safe (stack-limited)

Example of Macro Pitfall (Recursion):

#define CUBE(x) x*x*x
int z = CUBE(2+1);  // Expands to: 2+1*2+1*2+1 → 7 (not 27!)

Fix: Use parentheses:

#define CUBE(x) ((x)*(x)*(x))  // Now: ((2+1)*(2+1)*(2+1)) → 27

5. Preprocessing Directives

The preprocessor handles:

  1. Macro Definitions (#define, #undef)
  2. File Inclusion (#include <file.h> or #include "file.h")
  3. Conditional Compilation (#ifdef, #ifndef, #else, #endif)
  4. Line Control (#line, #error)

Example: Conditional Compilation

#define VERSION 2
#if VERSION == 1
    #include "old_code.h"
#else
    #include "new_code.h"
#endif

6. One-Pass vs. Multi-Pass Compilers

Feature One-Pass Compiler Multi-Pass Compiler
Phases Single scan of input Multiple scans (lexical → syntax → codegen)
Example Early C compilers (e.g., cc in 1970s) GCC, Clang, MSVC
Advantages Faster for simple languages Better optimization, modular design
Disadvantages Limited error recovery Higher memory usage
Use Case Embedded systems (resource-constrained) General-purpose compilers

Mermaid Diagram: Compiler Passes

flowchart TD
    A["Source Code"] --> B["Preprocessor<br/>(#define, #include)"]
    B --> C["Lexical Analyzer<br/>(Tokens)"]
    C --> D["Syntax Analyzer<br/>(AST)"]
    D --> E["Semantic Analyzer<br/>(Type Checking)"]
    E --> F["Intermediate Code<br/>(Three-address code)"]
    F --> G["Code Optimizer"]
    G --> H["Code Generator<br/>(Machine Code)"]
    H --> I["Linker<br/>(Executable)"]

7. Real-World Applications of Macros

In the Real World

  1. Linux Kernel (#define for Hardware Registers)

    • Macros like #define GPIO_PIN_0 0x01 map memory addresses to readable names.
    • Why? Avoids hardcoding hex values; improves maintainability.
  2. OpenCV (#define CV_PI 3.14159)

    • Predefined constants like CV_PI are used in image processing functions.
    • Example: circle(img, center, radius, CV_PI * radius, 2);
  3. Game Engines (Unity/Unreal)

    • #define MAX_PLAYERS 64 sets limits at compile-time.
    • Why? Prevents runtime errors from exceeding array bounds.
  4. Nepal’s eSewa (Conditional Macros for Feature Flags)

    • #ifdef DEBUG_MODE enables logging only in development builds.
    • Example: if (DEBUG_MODE) logTransaction();
  5. Khalti API (Header Guards)

    • #ifndef KHALTI_API_H prevents multiple inclusions of khalti.h.
    • Why? Avoids "redefinition" compiler errors.

8. Worked Example: Full Macro Expansion

Given:

#define MAX(a,b) ((a)>(b)?(a):(b))
#define SQUARE(x) ((x)*(x))
int main() {
    int x = MAX(3, SQUARE(2));
    return 0;
}

Step-by-Step Expansion:

  1. First Pass (Preprocessor):

    • Replace SQUARE(2) → ((2)*(2)) → 4
    • Replace MAX(3, 4) → ((3)>(4)?(3):(4)) → 4
    • Final expanded code:
      int main() {
          int x = ((3)>(4)?(3):(4));
          return 0;
      }
      
  2. Compiler Processes:

    • The expanded code is now parsed, type-checked, and compiled.

9. Common Macro Pitfalls & Fixes

Pitfall Example Fix
Missing Parentheses #define SQUARE(x) x*x #define SQUARE(x) ((x)*(x))
Argument Evaluation Order SQUARE(a++) → a*a++ (undefined) Use functions or parentheses
Macro Recursion #define FACT(n) n*FACT(n-1) Use functions or iterative macros
Side Effects in Arguments SQUARE(i++) → i*i++ (UB) Avoid macros for expressions with side effects

10. Exam Tip

  • For macro expansion questions:

    • Expand step-by-step, showing intermediate results.
    • Parentheses matter! Always add them to avoid incorrect grouping.
    • Trace conditional macros (#ifdef) by showing which blocks are included/excluded.
  • For one-pass vs. multi-pass:

    • One-pass: Faster, less error recovery (e.g., old C compilers).
    • Multi-pass: Slower but modular (e.g., GCC’s gcc -E for preprocessing).
  • Common exam questions:

    1. Expand #define MIN(a,b) ((a)<(b)?(a):(b)) for MIN(x+1, y*2).
    2. Differentiate macros from functions (execution time, type safety).
    3. Explain why SQUARE(2+3) gives 25 (not 16).

Visual Summary:

mindmap
  root((Macros & Preprocessing))
    Definition
      Text substitution before compilation
    Types
      Object-like
      Function-like
      Conditional
    Expansion
      Step-by-step replacement
    Pitfalls
      Missing parentheses
      Side effects
      Recursion
    Real-World
      Linux Kernel
      OpenCV
      eSewa
    Compiler Passes
      One-pass (fast, limited)
      Multi-pass (modular, optimized)

Based on the TU BSc CSIT syllabus for Compiler Design and Construction (CSC365), unit 12.

Discussion

Loading…