README
Macros and Definitions in C
Table of Contents
- •Introduction
- •Object-like Macros
- •Function-like Macros
- •Stringification Operator
- •Token Pasting Operator
- •Variadic Macros
- •Macro Best Practices
- •Common Pitfalls and Solutions
- •Predefined Macros
- •Practical Applications
Introduction
Macros are one of the most powerful features of the C preprocessor. They allow you to define constants, create inline code substitutions, and build complex code generation patterns. Understanding macros is essential for writing efficient, maintainable, and portable C code.
What is a Macro?
A macro is a fragment of code that is given a name. When the preprocessor encounters this name in the source code, it replaces it with the defined fragment before compilation. This process is called macro expansion.
#define PI 3.14159265359
// Every occurrence of PI will be replaced with 3.14159265359
Why Use Macros?
- •Symbolic Constants: Replace magic numbers with meaningful names
- •Code Reuse: Define common patterns once
- •Conditional Compilation: Enable/disable features at compile time
- •Performance: Avoid function call overhead for simple operations
- •Portability: Abstract platform-specific code
Macros vs. Functions
| Feature | Macros | Functions |
|---|---|---|
| Expansion | Compile-time | Runtime |
| Type Checking | None | Yes |
| Overhead | No call overhead | Call/return overhead |
| Code Size | May increase | Single copy |
| Debugging | Harder | Easier |
| Side Effects | Can be problematic | Predictable |
Object-like Macros
Object-like macros are the simplest form of macros. They define a name that represents a value or code fragment.
Basic Syntax
#define NAME replacement_text
Defining Constants
// Numeric constants
#define MAX_SIZE 100
#define PI 3.14159265359
#define BUFFER_SIZE 1024
// Character constants
#define NEWLINE '\n'
#define TAB '\t'
// String constants
#define VERSION "1.0.0"
#define PROGRAM_NAME "MyApp"
// Expression constants
#define DOUBLE_BUFFER (BUFFER_SIZE * 2)
#define ARRAY_SIZE (sizeof(array) / sizeof(array[0]))
Multi-line Macros
Use backslash (\) to continue a macro on the next line:
#define LONG_MESSAGE "This is a very long message that \
spans multiple lines in the source \
but is a single string"
#define ERROR_HEADER "===============================\n\
ERROR REPORT\n\
===============================\n"
Empty Macros
Macros can be defined without a value for conditional compilation:
#define DEBUG // Defined but empty
#define FEATURE_ENABLED
#ifdef DEBUG
// This code is included
#endif
Undefining Macros
Use #undef to remove a macro definition:
#define TEMPORARY_VALUE 100
// Use TEMPORARY_VALUE...
#undef TEMPORARY_VALUE
// TEMPORARY_VALUE is no longer defined
Redefining Macros
#define MAX 100
// To safely redefine:
#undef MAX
#define MAX 200
// Or check first:
#ifndef MAX
#define MAX 100
#endif
Function-like Macros
Function-like macros take parameters and can perform operations on them.
Basic Syntax
#define NAME(parameters) replacement_text
Important: No space between macro name and opening parenthesis!
#define SQUARE(x) ((x) * (x)) // Correct
#define SQUARE (x) ((x) * (x)) // Wrong! Treated as object-like macro
Simple Function-like Macros
// Math operations
#define SQUARE(x) ((x) * (x))
#define CUBE(x) ((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))
#define CLAMP(x, low, high) (MIN(MAX(x, low), high))
// Type-specific operations
#define SWAP(type, a, b) do { type temp = a; a = b; b = temp; } while(0)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
// Bit operations
#define SET_BIT(n, bit) ((n) | (1 << (bit)))
#define CLEAR_BIT(n, bit) ((n) & ~(1 << (bit)))
#define TOGGLE_BIT(n, bit) ((n) ^ (1 << (bit)))
#define CHECK_BIT(n, bit) (((n) >> (bit)) & 1)
Why Use Parentheses?
Parentheses prevent operator precedence issues:
// Bad: No parentheses
#define DOUBLE(x) x * 2
int result = DOUBLE(3 + 4); // Expands to: 3 + 4 * 2 = 11 (wrong!)
// Good: With parentheses
#define DOUBLE(x) ((x) * 2)
int result = DOUBLE(3 + 4); // Expands to: ((3 + 4) * 2) = 14 (correct!)
Multi-statement Macros
Use do { ... } while(0) for multi-statement macros:
// Problematic:
#define LOG_ERROR(msg) printf("Error: "); printf("%s\n", msg)
if (error)
LOG_ERROR("something wrong"); // Only first printf is conditional!
// Better:
#define LOG_ERROR(msg) do { \
printf("Error: "); \
printf("%s\n", msg); \
} while(0)
if (error)
LOG_ERROR("something wrong"); // Both statements are conditional
Why do { ... } while(0)?
- •Creates a single statement from multiple statements
- •Requires a semicolon after use (natural C syntax)
- •Works correctly in all control structures
- •Compiler optimizes away the loop (zero overhead)
Stringification Operator (#)
The # operator converts a macro parameter to a string literal.
Basic Usage
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
printf("%s\n", STRINGIFY(Hello World)); // Prints: Hello World
printf("%s\n", STRINGIFY(100 + 200)); // Prints: 100 + 200
Two-Level Stringification
To stringify a macro's value (not its name), use two levels:
#define VERSION 100
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
printf("%s\n", STRINGIFY(VERSION)); // Prints: VERSION
printf("%s\n", TOSTRING(VERSION)); // Prints: 100
Practical Applications
// Variable name as string
#define PRINT_VAR(var) printf(#var " = %d\n", var)
int count = 42;
PRINT_VAR(count); // Prints: count = 42
// Assert macro
#define ASSERT(condition) do { \
if (!(condition)) { \
fprintf(stderr, "Assertion failed: %s\n", #condition); \
fprintf(stderr, "File: %s, Line: %d\n", __FILE__, __LINE__); \
abort(); \
} \
} while(0)
ASSERT(x > 0); // If fails: "Assertion failed: x > 0"
// Debug printing
#define DEBUG_PRINT(expr) printf(#expr " = %d\n", (expr))
DEBUG_PRINT(a + b * c); // Prints: a + b * c = <value>
Token Pasting Operator (##)
The ## operator concatenates two tokens to form a new token.
Basic Usage
#define CONCAT(a, b) a ## b
int CONCAT(my, Var) = 10; // Creates: int myVar = 10;
int CONCAT(count, 1) = 100; // Creates: int count1 = 100;
Practical Applications
// Generate unique variable names
#define UNIQUE_VAR(name) CONCAT(name, __LINE__)
int UNIQUE_VAR(temp); // Creates: int temp42 (if on line 42)
// Create function names
#define DECLARE_GETTER(type, name) \
type get_##name(void) { return name; }
static int score = 100;
DECLARE_GETTER(int, score) // Creates: int get_score(void) { return score; }
// Type-generic functions
#define DEFINE_PRINT_FUNC(type, format) \
void print_##type(type value) { \
printf(format, value); \
}
DEFINE_PRINT_FUNC(int, "%d\n")
DEFINE_PRINT_FUNC(float, "%f\n")
DEFINE_PRINT_FUNC(char, "%c\n")
// Creates:
// void print_int(int value) { printf("%d\n", value); }
// void print_float(float value) { printf("%f\n", value); }
// void print_char(char value) { printf("%c\n", value); }
Combining # and
#define ENUM_ITEM(name) name,
#define ENUM_STRING(name) #name,
#define COLORS \
X(RED) \
X(GREEN) \
X(BLUE)
// Define enum
#define X(name) ENUM_ITEM(name)
enum Color { COLORS };
#undef X
// Define string array
#define X(name) ENUM_STRING(name)
const char* color_names[] = { COLORS };
#undef X
// color_names[RED] returns "RED"
Variadic Macros
Variadic macros accept a variable number of arguments.
Basic Syntax
#define MACRO_NAME(fixed_args, ...) replacement_text(__VA_ARGS__)
Simple Variadic Macros
// Print with format
#define PRINTF(...) printf(__VA_ARGS__)
PRINTF("Hello\n");
PRINTF("Value: %d\n", 42);
PRINTF("%s = %d\n", "x", 10);
// Print with prefix
#define LOG(format, ...) printf("[LOG] " format, __VA_ARGS__)
LOG("Value: %d\n", 42); // [LOG] Value: 42
Handling Empty Variable Arguments
// Problem: LOG("Simple message") fails because __VA_ARGS__ is empty
// Solution 1: Use ##__VA_ARGS__ (GCC extension)
#define LOG(format, ...) printf("[LOG] " format, ##__VA_ARGS__)
// Solution 2: C99 approach - always include at least one argument
#define LOG(format, ...) printf("[LOG] " format __VA_OPT__(,) __VA_ARGS__)
// Solution 3: First argument as format
#define LOG(...) printf("[LOG] " __VA_ARGS__)
Advanced Variadic Macros
// Debug logging with file and line
#define DEBUG(format, ...) do { \
fprintf(stderr, "[DEBUG %s:%d] " format "\n", \
__FILE__, __LINE__, ##__VA_ARGS__); \
} while(0)
DEBUG("Starting process");
DEBUG("Processing item %d of %d", i, total);
// Conditional logging
#ifdef ENABLE_LOGGING
#define LOG(level, ...) do { \
printf("[%s] ", level); \
printf(__VA_ARGS__); \
printf("\n"); \
} while(0)
#else
#define LOG(level, ...) ((void)0)
#endif
LOG("INFO", "Application started");
LOG("ERROR", "Failed to open file: %s", filename);
Counting Variable Arguments
// Count number of arguments (up to 10)
#define COUNT_ARGS(...) \
COUNT_ARGS_IMPL(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define COUNT_ARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
int count = COUNT_ARGS(a, b, c); // count = 3
Macro Best Practices
1. Use Uppercase for Macro Names
#define MAX_BUFFER_SIZE 1024 // Good
#define maxBufferSize 1024 // Bad - looks like a variable
2. Always Parenthesize
// Parenthesize the entire expression
#define AREA(w, h) ((w) * (h))
// Parenthesize each parameter
#define SQUARE(x) ((x) * (x))
// Parenthesize both
#define DOUBLE(x) (((x)) * 2) // Overkill but safe
3. Use Inline Functions When Possible (C99+)
// Prefer inline functions for type safety
static inline int square(int x) {
return x * x;
}
// Use macros when type-generic behavior is needed
#define SQUARE(x) ((x) * (x)) // Works with int, float, double
4. Document Complex Macros
/**
* @brief Safely allocates memory and checks for failure
* @param ptr Pointer variable to allocate to
* @param type The type to allocate
* @param count Number of elements to allocate
* @note Calls handle_allocation_failure() on failure
*/
#define SAFE_ALLOC(ptr, type, count) do { \
(ptr) = (type *)malloc(sizeof(type) * (count)); \
if ((ptr) == NULL) { \
handle_allocation_failure(__FILE__, __LINE__); \
} \
} while(0)
5. Avoid Side Effects
// Dangerous with side effects
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int result = MAX(x++, y++); // x or y incremented twice!
// Safe version using GCC extension
#define MAX_SAFE(a, b) ({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; \
})
6. Use Unique Names for Internal Variables
// Potential conflict
#define SWAP(a, b) do { int temp = a; a = b; b = temp; } while(0)
int temp = 10; // Conflicts!
SWAP(x, temp); // Broken!
// Safe version
#define SWAP(a, b) do { \
int _swap_temp_##__LINE__ = (a); \
(a) = (b); \
(b) = _swap_temp_##__LINE__; \
} while(0)
Common Pitfalls and Solutions
Pitfall 1: Missing Parentheses
// Problem
#define MULTIPLY(a, b) a * b
int result = MULTIPLY(2 + 3, 4 + 5); // = 2 + 3 * 4 + 5 = 19 (wrong!)
// Solution
#define MULTIPLY(a, b) ((a) * (b))
int result = MULTIPLY(2 + 3, 4 + 5); // = ((2 + 3) * (4 + 5)) = 45 (correct!)
Pitfall 2: Double Evaluation
// Problem
#define SQUARE(x) ((x) * (x))
int result = SQUARE(expensive_function()); // Called twice!
// Solution 1: Use inline function
static inline int square(int x) { return x * x; }
// Solution 2: GCC extension
#define SQUARE_SAFE(x) ({ \
__typeof__(x) _x = (x); \
_x * _x; \
})
Pitfall 3: Macro Expansion Order
// Problem
#define A 1
#define B A
#define A 2
// B is still 1, not 2 (A expanded when B was defined)
// Solution: Two-level macros
#define A 1
#define B() A // Function-like defers expansion
#define A 2
// B() now returns 2
Pitfall 4: Semicolon Issues
// Problem
#define DO_SOMETHING() statement1; statement2
if (condition)
DO_SOMETHING(); // Only statement1 is conditional!
// Solution
#define DO_SOMETHING() do { statement1; statement2; } while(0)
Pitfall 5: Spaces in Macro Definition
// Problem
#define FUNC (x) ((x) * 2) // Space makes it object-like!
// Expands to: (x) ((x) * 2), not a function call
// Solution
#define FUNC(x) ((x) * 2) // No space before parenthesis
Pitfall 6: Recursive Macros
// This won't work as expected
#define FOO (4 + FOO) // FOO is not recursively expanded
// Solution: Macros cannot be truly recursive
// Use functions for recursive behavior
Predefined Macros
C compilers provide several predefined macros:
Standard Macros (ANSI C)
| Macro | Description |
|---|---|
__FILE__ | Current source file name (string) |
__LINE__ | Current line number (integer) |
__DATE__ | Compilation date "Mmm dd yyyy" |
__TIME__ | Compilation time "hh:mm:ss" |
__STDC__ | 1 if compiler is ISO C compliant |
__STDC_VERSION__ | C standard version (e.g., 199901L for C99) |
GCC-Specific Macros
| Macro | Description |
|---|---|
__GNUC__ | GCC major version |
__GNUC_MINOR__ | GCC minor version |
__FUNCTION__ | Current function name |
__PRETTY_FUNCTION__ | Function name with signature |
__COUNTER__ | Unique incrementing counter |
Platform Detection
// Operating system
#ifdef _WIN32
// Windows
#elif defined(__linux__)
// Linux
#elif defined(__APPLE__)
// macOS
#endif
// Architecture
#ifdef __x86_64__
// 64-bit x86
#elif defined(__i386__)
// 32-bit x86
#elif defined(__arm__)
// ARM
#endif
Usage Examples
// Logging with file and line
#define LOG(msg) printf("[%s:%d] %s\n", __FILE__, __LINE__, msg)
// Compile-time information
printf("Compiled on %s at %s\n", __DATE__, __TIME__);
printf("Using C standard: %ld\n", __STDC_VERSION__);
// Function tracing
#define TRACE() printf("Entering %s\n", __FUNCTION__)
void my_function(void) {
TRACE(); // Prints: Entering my_function
// ...
}
// Build information
const char* build_info = "Built " __DATE__ " " __TIME__ " from " __FILE__;
Practical Applications
Debug Macros
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) \
fprintf(stderr, "[DEBUG %s:%d] " fmt "\n", \
__FILE__, __LINE__, ##__VA_ARGS__)
#define DEBUG_ASSERT(cond) do { \
if (!(cond)) { \
fprintf(stderr, "Assertion failed: %s\n", #cond); \
fprintf(stderr, "Location: %s:%d\n", __FILE__, __LINE__); \
abort(); \
} \
} while(0)
#define DEBUG_TRACE() \
fprintf(stderr, "[TRACE] %s() called\n", __FUNCTION__)
#else
#define DEBUG_PRINT(fmt, ...) ((void)0)
#define DEBUG_ASSERT(cond) ((void)0)
#define DEBUG_TRACE() ((void)0)
#endif
Error Handling Macros
#define CHECK_NULL(ptr) do { \
if ((ptr) == NULL) { \
fprintf(stderr, "Null pointer: %s\n", #ptr); \
return -1; \
} \
} while(0)
#define CHECK_ALLOC(ptr) do { \
if ((ptr) == NULL) { \
perror("Memory allocation failed"); \
exit(EXIT_FAILURE); \
} \
} while(0)
#define SAFE_FREE(ptr) do { \
if ((ptr) != NULL) { \
free(ptr); \
(ptr) = NULL; \
} \
} while(0)
Container Macros
// Get container from member pointer
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
// Array utilities
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define ARRAY_END(arr) ((arr) + ARRAY_SIZE(arr))
// Loop helpers
#define FOREACH(item, array) \
for (typeof((array)[0]) *item = (array); \
item < ARRAY_END(array); \
item++)
#define REPEAT(n) for (int _i = 0; _i < (n); _i++)
Type-Safe Generic Operations
// Generic swap using GCC extension
#define SWAP(a, b) do { \
__typeof__(a) _tmp = (a); \
(a) = (b); \
(b) = _tmp; \
} while(0)
// Generic min/max
#define MIN(a, b) ({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a < _b ? _a : _b; \
})
#define MAX(a, b) ({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; \
})
Enum-String Mapping (X-Macro Technique)
// Define the list once
#define STATUS_LIST \
X(STATUS_OK, "OK") \
X(STATUS_ERROR, "Error") \
X(STATUS_PENDING, "Pending") \
X(STATUS_COMPLETE, "Complete")
// Generate enum
#define X(name, str) name,
typedef enum { STATUS_LIST } Status;
#undef X
// Generate string array
#define X(name, str) [name] = str,
const char* status_strings[] = { STATUS_LIST };
#undef X
// Usage
Status s = STATUS_OK;
printf("Status: %s\n", status_strings[s]); // "OK"
Summary
Key Points
- •Object-like macros define simple text substitutions
- •Function-like macros accept parameters and perform operations
- •Stringification (#) converts macro parameters to strings
- •Token pasting (##) concatenates tokens to form new identifiers
- •Variadic macros accept variable numbers of arguments
Best Practices Checklist
- • Use uppercase names for macros
- • Parenthesize all parameters and the entire expression
- • Use
do { ... } while(0)for multi-statement macros - • Avoid side effects in macro arguments
- • Document complex macros thoroughly
- • Consider inline functions as alternatives
- • Use unique names for internal variables
- • Test macros with edge cases
When to Use Macros vs. Functions
| Use Macros When | Use Functions When |
|---|---|
| Need compile-time constants | Need type safety |
| Need type-generic operations | Need debugging support |
| Need code at specific location | Have complex logic |
| Need zero runtime overhead | Need recursion |
| Need stringification/pasting | Have side effects in args |
Further Reading
- •"The C Programming Language" by Kernighan and Ritchie
- •"C: A Reference Manual" by Harbison and Steele
- •GCC Preprocessor Documentation
- •C99/C11 Standard Preprocessor Specifications
This document is part of the C Programming Language course. For practical exercises, see the accompanying exercises.c file.