Macros

Introduction

Macros in C are preprocessor directives that allow text replacement before compilation. They provide a way to define constants, create function-like code snippets, and perform conditional compilation. Macros are processed by the preprocessor, which replaces macro calls with their definitions before the actual compilation begins.

Key Concepts

Preprocessor: Processes macros before compilation Text Replacement: Macros are literally replaced with their definitions No Type Checking: Macros don’t have data types Compile-time: Replacement happens at compile time, not runtime

Types of Macros

Object-like Macros (Simple Macros)

#include <stdio.h>

#define PI 3.14159
#define MAX_SIZE 100
#define COMPANY_NAME "TechCorp"
#define DEBUG 1

int main() {
    double radius = 5.0;
    double area = PI * radius * radius;
    
    printf("Company: %s\n", COMPANY_NAME);
    printf("Area of circle: %.2f\n", area);
    printf("Maximum size: %d\n", MAX_SIZE);
    
    return 0;
}

Function-like Macros

#include <stdio.h>

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))

int main() {
    int num1 = 5, num2 = 8;
    
    printf("Square of %d: %d\n", num1, SQUARE(num1));
    printf("Max of %d and %d: %d\n", num1, num2, MAX(num1, num2));
    printf("Min of %d and %d: %d\n", num1, num2, MIN(num1, num2));
    printf("Absolute value of -10: %d\n", ABS(-10));
    
    return 0;
}

Macro Definition Syntax

Basic Definition

#define MACRO_NAME replacement_text
#define MACRO_FUNCTION(params) replacement_text

Examples with Different Data Types

#include <stdio.h>

#define TRUE 1
#define FALSE 0
#define NEWLINE '\n'
#define ERROR_MSG "An error occurred"
#define BUFFER_SIZE 1024

#define PRINT_INT(x) printf("Integer: %d\n", x)
#define PRINT_FLOAT(x) printf("Float: %.2f\n", x)

int main() {
    int status = TRUE;
    char message[] = ERROR_MSG;
    
    PRINT_INT(42);
    PRINT_FLOAT(3.14);
    
    printf("Status: %d%c", status, NEWLINE);
    printf("Message: %s\n", message);
    
    return 0;
}

Macro with Multiple Statements

Using do-while(0) Pattern

#include <stdio.h>

#define SWAP(a, b) do { \
    int temp = a; \
    a = b; \
    b = temp; \
} while(0)

#define DEBUG_PRINT(msg) do { \
    printf("[DEBUG] %s\n", msg); \
    fflush(stdout); \
} while(0)

int main() {
    int x = 10, y = 20;
    
    printf("Before swap: x=%d, y=%d\n", x, y);
    DEBUG_PRINT("Performing swap operation");
    SWAP(x, y);
    printf("After swap: x=%d, y=%d\n", x, y);
    
    return 0;
}

Conditional Compilation

#ifdef, #ifndef, #endif

#include <stdio.h>

#define DEBUG_MODE
#define VERSION 2

int main() {
    printf("Program started\n");
    
#ifdef DEBUG_MODE
    printf("[DEBUG] Debug mode is enabled\n");
#endif

#ifndef RELEASE_MODE
    printf("Running in development mode\n");
#endif

#if VERSION >= 2
    printf("Using version 2.0 features\n");
#else
    printf("Using older version features\n");
#endif

    return 0;
}

Built-in Predefined Macros

#include <stdio.h>

int main() {
    printf("File: %s\n", __FILE__);
    printf("Line: %d\n", __LINE__);
    printf("Date: %s\n", __DATE__);
    printf("Time: %s\n", __TIME__);
    printf("Function: %s\n", __func__);
    
    return 0;
}

Macro Operators

Stringizing Operator (#)

#include <stdio.h>

#define STRINGIFY(x) #x
#define PRINT_VAR(var) printf(#var " = %d\n", var)

int main() {
    int age = 25;
    int score = 95;
    
    printf("Stringified: %s\n", STRINGIFY(Hello World));
    
    PRINT_VAR(age);
    PRINT_VAR(score);
    
    return 0;
}

Token Pasting Operator (##)

#include <stdio.h>

#define CONCAT(a, b) a##b
#define DECLARE_VAR(type, name) type var_##name

int main() {
    DECLARE_VAR(int, count);     // Creates: int var_count;
    DECLARE_VAR(float, price);   // Creates: float var_price;
    
    var_count = 10;
    var_price = 19.99;
    
    printf("Count: %d\n", var_count);
    printf("Price: %.2f\n", var_price);
    
    // Token pasting for identifiers
    int CONCAT(num, 1) = 100;    // Creates: int num1 = 100;
    printf("num1: %d\n", num1);
    
    return 0;
}

Practical Examples

Mathematical Macros

#include <stdio.h>

#define AREA_CIRCLE(r) (3.14159 * (r) * (r))
#define AREA_RECTANGLE(l, w) ((l) * (w))
#define PERIMETER_CIRCLE(r) (2 * 3.14159 * (r))
#define CELSIUS_TO_FAHRENHEIT(c) (((c) * 9.0 / 5.0) + 32)

int main() {
    double radius = 5.0;
    double length = 10.0, width = 6.0;
    double temp_c = 25.0;
    
    printf("Circle area (r=%.1f): %.2f\n", radius, AREA_CIRCLE(radius));
    printf("Rectangle area (%.1fx%.1f): %.2f\n", length, width, AREA_RECTANGLE(length, width));
    printf("Circle perimeter (r=%.1f): %.2f\n", radius, PERIMETER_CIRCLE(radius));
    printf("%.1f°C = %.1f°F\n", temp_c, CELSIUS_TO_FAHRENHEIT(temp_c));
    
    return 0;
}

Utility Macros

#include <stdio.h>

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define IS_EVEN(x) ((x) % 2 == 0)
#define IS_ODD(x) ((x) % 2 != 0)
#define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))

int main() {
    int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int size = ARRAY_SIZE(numbers);
    
    printf("Array size: %d\n", size);
    
    for (int i = 0; i < size; i++) {
        printf("%d is %s\n", numbers[i], IS_EVEN(numbers[i]) ? "even" : "odd");
    }
    
    int value = 15;
    int clamped = CLAMP(value, 5, 10);
    printf("Clamping %d to range [5,10]: %d\n", value, clamped);
    
    return 0;
}

Common Pitfalls

Side Effects with Multiple Evaluations

#include <stdio.h>

// Dangerous macro - evaluates argument multiple times
#define BAD_MAX(a, b) ((a) > (b) ? (a) : (b))

// Better approach using do-while
#define SAFE_MAX(a, b) ({ \
    typeof(a) _a = (a); \
    typeof(b) _b = (b); \
    (_a > _b) ? _a : _b; \
})

int main() {
    int x = 5, y = 3;
    
    // This increments x twice!
    int result1 = BAD_MAX(++x, y);
    printf("Result1: %d, x is now: %d\n", result1, x);
    
    x = 5; // Reset
    // This only increments x once
    int result2 = SAFE_MAX(++x, y);
    printf("Result2: %d, x is now: %d\n", result2, x);
    
    return 0;
}

Operator Precedence Issues

#include <stdio.h>

// Wrong - missing parentheses
#define BAD_SQUARE(x) x * x

// Correct - with parentheses
#define GOOD_SQUARE(x) ((x) * (x))

int main() {
    int result1 = BAD_SQUARE(2 + 3);   // Expands to: 2 + 3 * 2 + 3 = 11
    int result2 = GOOD_SQUARE(2 + 3);  // Expands to: ((2 + 3) * (2 + 3)) = 25
    
    printf("Bad square result: %d\n", result1);
    printf("Good square result: %d\n", result2);
    
    return 0;
}

Undefining Macros

#include <stdio.h>

#define TEMP_MACRO 100

int main() {
    printf("TEMP_MACRO: %d\n", TEMP_MACRO);
    
#undef TEMP_MACRO
    
    // This would cause an error now:
    // printf("TEMP_MACRO: %d\n", TEMP_MACRO);
    
    // Can redefine after undefining
#define TEMP_MACRO 200
    printf("New TEMP_MACRO: %d\n", TEMP_MACRO);
    
    return 0;
}

Best Practices

  1. Use ALL_CAPS: For macro names to distinguish from variables
  2. Parenthesize Everything: Wrap parameters and entire expression in parentheses
  3. Avoid Side Effects: Be careful with macros that evaluate arguments multiple times
  4. Use Functions: For complex logic, prefer functions over macros
  5. Document Macros: Comment complex macro definitions
  6. Test Thoroughly: Macros can behave unexpectedly

Summary

Macros provide powerful text replacement capabilities in C, useful for defining constants, creating utility functions, and conditional compilation. While they offer performance benefits by avoiding function call overhead, they must be used carefully to avoid side effects and operator precedence issues. Modern C programming often favors inline functions over complex macros for better type safety and debugging capabilities. Understanding macros is essential for reading existing code and implementing efficient, reusable code patterns.


Part of BCA Programming with C Course (UGCOA22J201)