Lesson 11 of 12

The Preprocessor & Multi-File Programs

Learn what the preprocessor does before compilation, and how to split a program across several files using headers.

C
/* mathutils.h — the INTERFACE: types, declarations, macros */
#ifndef MATHUTILS_H            /* include guard: prevents double inclusion */
#define MATHUTILS_H

#include <stddef.h>

#define PI 3.14159265358979323846
#define MAX(a, b) ((a) > (b) ? (a) : (b))     /* parenthesise every argument! */
#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0])

typedef struct {
    double x, y;
} Vec2;

double vec2_length(Vec2 v);
Vec2   vec2_add(Vec2 a, Vec2 b);
int    clamp(int value, int low, int high);

#endif /* MATHUTILS_H */
C
/* mathutils.c — the IMPLEMENTATION */
#include "mathutils.h"      /* quotes: your own headers. <> : system headers */
#include <math.h>

double vec2_length(Vec2 v) {
    return sqrt(v.x * v.x + v.y * v.y);
}

Vec2 vec2_add(Vec2 a, Vec2 b) {
    return (Vec2){ a.x + b.x, a.y + b.y };     /* compound literal */
}

int clamp(int value, int low, int high) {
    if (value < low)  return low;
    if (value > high) return high;
    return value;
}

/* static = internal linkage: visible only inside this file */
static int helper_nobody_else_sees(int n) { return n * 2; }


/* main.c */
#include <stdio.h>
#include "mathutils.h"

int main(void) {
    Vec2 a = {3, 4};
    printf("%.1f\n", vec2_length(a));          /* 5.0 */
    printf("%d\n", MAX(10, 3));
    printf("%d\n", clamp(120, 0, 100));        /* 100 */
    return 0;
}
Bash
# Compile each .c to an object file, then link them together
gcc -Wall -Wextra -std=c17 -c mathutils.c -o mathutils.o
gcc -Wall -Wextra -std=c17 -c main.c      -o main.o
gcc mathutils.o main.o -o app -lm         # -lm links the math library

# Or in one step
gcc -Wall -Wextra -std=c17 main.c mathutils.c -o app -lm

# A minimal Makefile
# CC     = gcc
# CFLAGS = -Wall -Wextra -std=c17 -g
# app: main.o mathutils.o
# 	$(CC) $^ -o $@ -lm
# %.o: %.c mathutils.h
# 	$(CC) $(CFLAGS) -c $< -o $@
# clean:
# 	rm -f *.o app

# See exactly what the preprocessor produced
gcc -E main.c | less
C
#include <stdio.h>
#include <assert.h>

/* Conditional compilation */
#define DEBUG 1

#if DEBUG
    #define LOG(fmt, ...) fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#else
    #define LOG(fmt, ...) ((void)0)
#endif

/* Platform differences */
#ifdef _WIN32
    #define PATH_SEP '\\'
#else
    #define PATH_SEP '/'
#endif

/* Predefined macros: __FILE__ __LINE__ __func__ __DATE__ __STDC_VERSION__ */

int divide(int a, int b) {
    assert(b != 0 && "divide by zero");   /* removed entirely when NDEBUG is defined */
    return a / b;
}

int main(void) {
    LOG("starting with sep %c", PATH_SEP);
    printf("%d\n", divide(10, 2));
    return 0;
}
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.