Preprocessor and Macros in C
The preprocessor runs before compilation, performing text substitution. Macros and #if directives enable portable, configurable code.
#define Macros
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
#define PI 3.141592653589793
/* Always parenthesize macro parameters */
printf("%d\n", SQUARE(1 + 2)); /* 9, not 5 */
Include Guards
#ifndef MYHEADER_H
#define MYHEADER_H
/* declarations */
#endif
Prefer #pragma once where supported (non-standard but widely available).
Conditional Compilation
#ifdef DEBUG
#define LOG(msg) fprintf(stderr, msg)
#else
#define LOG(msg) ((void)0)
#endif
#if defined(__linux__)
/* Linux-specific code */
#elif defined(_WIN32)
/* Windows-specific code */
#endif
Predefined Macros
printf("File: %s, Line: %d\n", __FILE__, __LINE__);
printf("Date: %s\n", __DATE__);
#if __STDC_VERSION__ >= 201112L
/* C11+ features available */
#endif
Modern Alternatives
/* Prefer static inline over function-like macros */
static inline int max(int a, int b) {
return a > b ? a : b;
}
/* Prefer _Static_assert over compile-time checks in macros */
_Static_assert(sizeof(int) >= 4, "int must be at least 32 bits");
Common Pitfalls
- Treating compiler warnings as optional rather than actionable feedback.
- Skipping error checks on library and system calls.
- Copy-pasting examples without adapting to your project’s conventions.
Best Practices
- Enable strict compiler warnings and fix them before merging.
- Write small, testable units with clear input/output contracts.
- Document non-obvious invariants and preconditions.
- Use version control and code review for every change.
Memory and Performance Notes
Complex macros expand at every call site — no type checking. Prefer static inline functions for debuggability.
Exercise
Create a header with include guards defining ARRAY_SIZE(arr) and MIN/MAX macros. Write a test file.
Hint: ARRAY_SIZE: (sizeof(arr) / sizeof((arr)[0])) — only works on arrays, not pointers.
Summary
Apply these concepts in small programs before moving to larger projects. Combine with adjacent topics in the learning path for deeper mastery.
Real-World Application
These concepts appear in production codebases — from operating system kernels to embedded firmware. Study open-source projects that use this topic extensively to see idiomatic patterns at scale.
Debugging Checklist
- Reproduce the issue with the smallest possible input.
- Enable compiler warnings and sanitizers.
- Use a debugger to inspect state at the failure point.
- Verify assumptions about types, sizes, and return values.
- Compare working and broken code paths side by side.
- Write a regression test once the bug is fixed.
Further Reading
Consult the ISO C standard, Effective C by Robert C. Seacord, and your compiler documentation for platform-specific behavior.
Quick Reference
Review the code examples on this page before starting the exercise. Type them manually to build muscle memory.
Additional Examples
Consider how this topic applies in a larger project:
// Break the problem into smaller functions
// Test each function independently
// Integrate incrementally
Working through variations of the examples above builds deeper understanding than reading alone.
Interview and Review Questions
- Explain the core concept of this topic in your own words.
- What happens when this code runs with edge-case input (empty, null, zero, max value)?
- How would you debug a bug related to this topic in production?
- What are the performance implications of the approach shown here?
- How does this feature compare to the equivalent in another language you know?