Skip to main content

C Macros

What Are Macros?

Macros in C are a powerful preprocessor feature that allows you to define reusable code fragments. Unlike functions, macros operate at the preprocessor level, which means they are processed before the actual compilation begins. The preprocessor literally replaces each macro call with the macro's definition, a process known as macro expansion.

Macro Basics

Macros are defined using the #define preprocessor directive. The basic syntax is:

c
#define MACRO_NAME replacement_text

When the preprocessor encounters MACRO_NAME in your source code, it replaces it with replacement_text.

Simple Macros

c
#define PI 3.14159
#define MAX_SIZE 100
#define TRUE 1
#define FALSE 0

int main() {
float radius = 5.0;
float area = PI * radius * radius;

// After preprocessing, this becomes:
// float area = 3.14159 * radius * radius;

return 0;
}

Function-like Macros

Macros can also accept parameters, making them similar to functions:

c
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main() {
int num = 5;
int squared = SQUARE(num); // Expands to ((num) * (num))

int a = 10, b = 20;
int max = MAX(a, b); // Expands to ((a) > (b) ? (a) : (b))

return 0;
}
caution

Always wrap macro parameters in parentheses to avoid unexpected behavior due to operator precedence.

Advantages of Macros

  1. Performance: Since macros are expanded during preprocessing, there's no function call overhead.
  2. Type Independence: Macros can work with any data type.
  3. Code Reusability: Define once, use anywhere in your code.
c
#define SQUARE(x) ((x) * (x))

int main() {
int i = 5;
float f = 2.5;

int i_squared = SQUARE(i); // Works with int
float f_squared = SQUARE(f); // Works with float

return 0;
}

Common Pitfalls and How to Avoid Them

1. Side Effects

c
#define SQUARE(x) (x * x)

int main() {
int a = 5;
int result = SQUARE(a++); // Expands to (a++ * a++)
// Now a is 7, not 6, and result is 30, not 36

return 0;
}

Solution: Avoid using expressions with side effects in macro arguments.

2. Missing Parentheses

c
// Incorrect
#define SQUARE(x) x * x

int main() {
int result = 10 / SQUARE(2); // Expands to 10 / 2 * 2 = 10, not 2.5

return 0;
}

Solution: Always wrap the entire macro expansion and each parameter in parentheses.

c
// Correct
#define SQUARE(x) ((x) * (x))

Advanced Macro Techniques

String Conversion (# Operator)

The # operator (stringizing) converts a macro parameter into a string literal:

c
#define STRINGIFY(x) #x

int main() {
printf("%s\n", STRINGIFY(Hello World)); // Prints "Hello World"
return 0;
}

Token Concatenation (## Operator)

The ## operator (token pasting) combines two tokens into one:

c
#define CONCAT(a, b) a##b

int main() {
int xy = 10;
printf("%d\n", CONCAT(x, y)); // Accesses the variable xy

return 0;
}

Multiline Macros

For complex macros that span multiple lines, use the backslash character:

c
#define COMPLEX_OPERATION(x, y) do { \
int temp = (x) + (y); \
printf("Sum: %d\n", temp); \
temp = (x) * (y); \
printf("Product: %d\n", temp); \
} while(0)

The do { ... } while(0) construct ensures the macro can be used safely in all contexts, including in if statements.

Conditional Compilation

Macros are often used for conditional compilation:

c
#define DEBUG 1

int main() {
int x = 10;

#if DEBUG
printf("Debug: x = %d\n", x);
#endif

return 0;
}

Predefined Macros

C provides several predefined macros:

MacroDescription
__FILE__Current source file name
__LINE__Current line number in the source file
__DATE__Compilation date in "Mmm dd yyyy" format
__TIME__Compilation time in "hh:mm:ss" format
__STDC__Defined as 1 when compiler complies with the ANSI C standard
c
void debug_print(const char* message) {
printf("[%s:%d] %s\n", __FILE__, __LINE__, message);
}

Best Practices for Using Macros

  1. Use UPPERCASE names for macros to distinguish them from variables and functions.
  2. Consider using inline functions instead of function-like macros in modern C.
  3. Document complex macros thoroughly.
  4. Always parenthesize macro arguments and the entire macro expansion.
  5. Avoid side effects in macro arguments.
  6. Limit macro scope using #undef when appropriate.
c
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// After using MAX in a limited scope
#undef MAX

Summary

C macros are a powerful preprocessor feature that can help make your code more readable, maintainable, and efficient. However, with great power comes great responsibility. Be aware of the potential pitfalls and follow best practices to use macros effectively in your C programs.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)