C for People Who Already Know How to Code
The Preprocessor
It runs before the compiler even knows what a type is
Macro expansion is textual substitution. It happens in a pass before parsing, so a macro has no idea what a function, a type, or an expression even is. It's find-and-replace with parameters. Most preprocessor bugs come from forgetting that and treating a macro like a function.
Always parenthesize macro arguments, and the whole expansion
#define SQUARE(x) x*x
SQUARE(a + b) // expands to: a + b*a + b, not (a+b)*(a+b)
#define SQUARE_FIXED(x) ((x) * (x))
SQUARE_FIXED(a + b) // expands to: ((a + b) * (a + b)), correctA macro isn't a function: arguments can be evaluated more than once
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int i = 5, j = 3;
int m = MAX(i++, j); // i++ appears twice in the expansion, i gets incremented TWICEThere's no fix that keeps this a macro. This is exactly the class of bug that static inline functions exist to solve in C99+. If you need type-generic behavior a function can't give you, a macro is still the tool, but document loudly that arguments must be side-effect-free.
Header guards and #pragma once
Both solve the same problem: a header included twice in one translation unit re-declaring everything and breaking the build. #pragma once is one line, universally supported by every compiler that matters, but not part of the ISO standard. A guard is fully portable but needs a genuinely unique macro name (collision between two headers both named UTILS_H is a real, if rare, bug):
#ifndef MYLIB_WIDGET_H
#define MYLIB_WIDGET_H
// declarations
#endif // MYLIB_WIDGET_HThe transitive-include trap
If your .c file uses size_t because some header you included happens to include <stddef.h> itself, your file compiles today and breaks the moment that header's internals change and stop pulling it in, through no change of your own. Include what you use, directly, every time, regardless of what currently happens to work by accident.
X-macros: a pattern you'll meet in real code even if you never write one
An X-macro is a single list, defined once, expanded multiple times against different macro definitions: a compact way to keep an enum and its string names (or any other parallel table) from drifting apart:
#define COLOR_LIST \
X(RED) \
X(GREEN) \
X(BLUE)
enum color {
#define X(name) COLOR_##name,
COLOR_LIST
#undef X
};
static const char *color_names[] = {
#define X(name) #name,
COLOR_LIST
#undef X
};Add one entry to COLOR_LIST and both the enum and the name table update together. Ugly to read the first time, genuinely useful the moment you have more than one table that has to stay in sync with an enum by hand.