A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
A function-like macro is a #define with parentheses: #define SQUARE(x) ((x) * (x)). The preprocessor replaces every invocation with the expanded text before the compiler sees the code. There is no type checking. There is no function call overhead. There is also no protection against double evaluation, operator precedence bugs, and side-effect duplication. Function-like macros are powerful and dangerous in equal measure. Every rule you learn here exists because someone violated it and shipped a bug.
#include <stdio.h>#define BAD_SQUARE(x) x * x#define GOOD_SQUARE(x) ((x) * (x))int main(void) {printf("BAD_SQUARE(3 + 1): %d\n", BAD_SQUARE(3 + 1));printf("GOOD_SQUARE(3 + 1): %d\n", GOOD_SQUARE(3 + 1));printf("BAD_SQUARE(5): %d\n", BAD_SQUARE(5));printf("GOOD_SQUARE(5): %d\n", GOOD_SQUARE(5));return 0;}
BAD_SQUARE(3 + 1) expands to 3 + 1 * 3 + 1, which is 3 + 3 + 1 = 7. Operator precedence strikes. GOOD_SQUARE(3 + 1) expands to ((3 + 1) * (3 + 1)), which is 16. The rule: parenthesize every parameter in the body, and parenthesize the entire body. No exceptions. Not even when you think the caller will only pass simple values.
#include <stdio.h>#define MAX(a, b) ((a) > (b) ? (a) : (b))int main(void) {int x = 5;int result = MAX(x++, 3);printf("result: %d, x: %d\n", result, x);return 0;}
x++ was evaluated twice. The macro expanded to ((x++) > (3) ? (x++) : (3)). The first x++ returned 5 and incremented x to 6. The comparison was true, so the second x++ returned 6 and incremented x to 7. The result is 6, and x is 7. A function would have evaluated x++ once. This is why macros with side-effect arguments are bugs. Never pass expressions with side effects to a macro.
If the macro body is more than a simple expression, use a function. If the arguments might have side effects, use a function. If you need type safety, use a function. If you need to take the address of the operation, use a function. Macros are appropriate for simple arithmetic wrappers, compile-time constants, and the do-while(0) pattern we cover later. For everything else, inline functions give you the same performance with none of the text-substitution hazards.
Function-like macros are text substitution with no understanding of C semantics. Parenthesize everything. Avoid side-effect arguments. Prefer inline functions for anything non-trivial. The preprocessor is a blunt instrument. Use it for what it is good at: constants, conditionals, and code generation. Next: #ifdef and #ifndef, the conditional compilation directives that control which code the compiler sees.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.