Preprocessor, headers and multi-file projects
Use macros without the traps, put declarations in headers and definitions in .c files, and control visibility with static and extern.
Macros and their traps
/* prefer these */
enum { MAX_ITEMS = 16 }; /* a real type-checked constant */
static const double PI = 3.14159265358979;
static inline int square(int x) { return x * x; }
/* use a macro only when you need the type or the caller's scope */
#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0]) /* arrays only, not pointers */
#define MAX(a, b) ((a) > (b) ? (a) : (b)) /* arguments parenthesised */- Wrap every parameter in parentheses and the whole expression too, or
MAX(a, b+1) * 2will produce the wrong answer. - Macro arguments are evaluated once per use, so
MAX(i++, j)incrementsitwice. Astatic inlinefunction does not. ARRAY_LENsilently gives a wrong answer when passed a pointer instead of an array, becausesizeofthen yields the pointer size.- Multi-statement macros should be wrapped in
do { ... } while (0)so they behave like one statement inside anifwithout braces. - Use
#to stringify and##to paste tokens, and remember that a macro named like a function breaks any real function of that name.
#define LOG(fmt, ...) fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define CHECK(cond, ...) do { if (!(cond)) { LOG("check failed: " #cond, __VA_ARGS__); } } while (0)The header and source split
A header declares what other files may use; a .c file defines it. Every header needs an include guard, and anything not in the public header should be static so the linker never sees it.
/* stack.h — the public contract */
#ifndef STACK_H
#define STACK_H
#include <stddef.h>
#include <stdbool.h>
typedef struct Stack Stack; /* opaque: callers cannot see the fields */
Stack *stack_new(void);
void stack_free(Stack *s);
bool stack_push(Stack *s, int value);
bool stack_pop(Stack *s, int *out);
size_t stack_size(const Stack *s);
#endif /* STACK_H *//* stack.c — the implementation, with file-local helpers */
#include "stack.h"
#include <stdlib.h>
#define INITIAL_CAP 8
struct Stack { int *items; size_t len, cap; };
static bool grow(Stack *s) { /* static: no external linkage */
size_t nc = s->cap ? s->cap * 2 : INITIAL_CAP;
int *p = realloc(s->items, nc * sizeof *p);
if (!p) return false;
s->items = p; s->cap = nc;
return true;
}
Stack *stack_new(void) {
Stack *s = malloc(sizeof *s);
if (!s) return NULL;
s->items = NULL; s->len = s->cap = 0;
return s;
}
bool stack_push(Stack *s, int value) {
if (s->len == s->cap && !grow(s)) return false;
s->items[s->len++] = value;
return true;
}
void stack_free(Stack *s) { if (s) { free(s->items); free(s); } }| Keyword | Meaning | Where it belongs |
|---|---|---|
static on a function | Internal linkage: invisible outside this translation unit | Helper functions in the .c file |
static on a file-scope variable | One private global per translation unit | Implementation state, not headers |
static inside a function | One persistent object initialised once | Caches and counters |
extern | Declaration only; the definition lives elsewhere | Global variables shared across files |
inline in a header | Suggests inlining; needs care across translation units | Small hot helpers, with static inline for safety |
💡
Never define a function or a variable in a header. If two
.c files include it, the linker reports a duplicate symbol. Headers declare; .c files define.FAQ
Is #pragma once safe?
Every mainstream compiler supports it and it avoids guard-name collisions. Include guards are strictly portable and work with files copied around. Either is fine; do not use both in the same file.
My global variable causes a multiple definition error. Why?
You put the definition in a header that several translation units include. Keep
extern int counter; in the header and int counter = 0; in exactly one .c file.Related
Building and linking: static and shared libraries Syntax and the compiled toolchain
Last refreshed 2026-09-18.