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) * 2 will produce the wrong answer.
  • Macro arguments are evaluated once per use, so MAX(i++, j) increments i twice. A static inline function does not.
  • ARRAY_LEN silently gives a wrong answer when passed a pointer instead of an array, because sizeof then yields the pointer size.
  • Multi-statement macros should be wrapped in do { ... } while (0) so they behave like one statement inside an if without 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); } }
KeywordMeaningWhere it belongs
static on a functionInternal linkage: invisible outside this translation unitHelper functions in the .c file
static on a file-scope variableOne private global per translation unitImplementation state, not headers
static inside a functionOne persistent object initialised onceCaches and counters
externDeclaration only; the definition lives elsewhereGlobal variables shared across files
inline in a headerSuggests inlining; needs care across translation unitsSmall 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.

Building and linking: static and shared libraries Syntax and the compiled toolchain

Last refreshed 2026-09-18.