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
- Use ALL_CAPS: For macro names to distinguish from variables
- Parenthesize Everything: Wrap parameters and entire expression in parentheses
- Avoid Side Effects: Be careful with macros that evaluate arguments multiple times
- Use Functions: For complex logic, prefer functions over macros
- Document Macros: Comment complex macro definitions
- 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)