Skip to content

Mini‑Guide: Separate File Compilation in C (with Makefile)

This mini‑guide shows how to split C programs across multiple source files, compile them separately, and link them together using gcc and a Makefile.


1) Why separate compilation?

  • Organization: keep related functions in their own files (e.g., numhelpers.c for numeric helpers).
  • Rebuild speed: only recompile files that changed.
  • Reuse: share common utilities across multiple programs (numinfo, numconv both use numhelpers).

The notes sketch two programs (numinfo, numconv) that each depend on a common helper module and a header declaring the helper functions.


2) File layout for Project01

numinfo.c        # uses helpers
numconv.c        # uses helpers
numhelpers.c     # implements helpers
numhelpers.h     # declares helpers (function prototypes)

Create numhelpers.h with prototypes (as in the notes):

// numhelpers.h
#ifndef NUMHELPERS_H
#define NUMHELPERS_H

#include <stdbool.h>

bool is_dec_digit(char c);
// add more prototypes here

#endif

Include it at the top of every C file that calls these helpers:

#include <stdio.h>
#include "numhelpers.h"


The notes show direct compile/link commands (PDF p.2):

gcc -c numhelpers.c          # -> numhelpers.o
gcc -c numinfo.c             # -> numinfo.o
gcc -o numinfo numinfo.o numhelpers.o

gcc -c numconv.c             # -> numconv.o
gcc -o numconv numconv.o numhelpers.o

What happens? Each -c step turns a .c into an object file (.o). The final gcc -o … step links those .o files together with the C runtime “startup” code, producing an executable.


Below is your current Makefile (for reference), followed by an improved version.
(Current content pulled from inclass/week02.)

Your Makefile (verbatim)
.PHONY: all clean
CC      := gcc
CFLAGS  := -g -O2 -Wall -Wextra
LDFLAGS := -g

PROGS := echorepeat echorepeat_cfg
ECHOREPEAT_OBJS := echorepeat.o
ECHOREPEAT_CFG_OBJS := echorepeat_cfg.o

OBJS := ${ECHOREPEAT_OBJS} ${ECHOREPEAT_CFG_OBJS}

%.o: %.c
    ${CC} -c ${CFLAGS} -o $@ $<

all: ${PROGS}

echorepeat: ${ECHOREPEAT_OBJS}
    ${CC} ${LDFLAGS} -o $@ $^

echorepeat_cfg: ${ECHOREPEAT_CFG_OBJS}
    ${CC} ${LDFLAGS} -o $@ $^


clean:
    rm -rf ${PROGS} ${OBJS}

A tidy, dependency‑aware Makefile

This version captures the structure from the notes (two programs share one helper module) and uses automatic variables and pattern rules so you only describe each piece once.

# Compiler and flags
CC      := gcc
CFLAGS  := -Wall -Wextra -Wpedantic -std=c11 -O2
LDFLAGS :=

# Programs we build
PROGS := numinfo numconv

# Headers
HEADERS := numhelpers.h

# Common module
COMMON_SRCS := numhelpers.c
COMMON_OBJS := $(COMMON_SRCS:.c=.o)

# Per-program sources
NUMINFO_SRCS := numinfo.c
NUMCONV_SRCS := numconv.c

# Derive object lists
NUMINFO_OBJS := $(NUMINFO_SRCS:.c=.o) $(COMMON_OBJS)
NUMCONV_OBJS := $(NUMCONV_SRCS:.c=.o) $(COMMON_OBJS)

.PHONY: all clean format

all: $(PROGS)

numinfo: $(NUMINFO_OBJS)
    $(CC) $(LDFLAGS) -o $@ $^

numconv: $(NUMCONV_OBJS)
    $(CC) $(LDFLAGS) -o $@ $^

# Generic compile rule: .c -> .o
%.o: %.c $(HEADERS)
    $(CC) $(CFLAGS) -c $< -o $@

# Helpful extras
format:
    clang-format -i *.c *.h || true

clean:
    rm -f $(PROGS) *.o

How to use: - make or make all builds numinfo and numconv. - Edit only one .c file and make will recompile just that file plus anything that depends on it. - make clean removes products.

Tip: keep function declarations in numhelpers.h and definitions in numhelpers.c. Any .c using those functions should #include "numhelpers.h" so dependencies are tracked correctly.


5) Minimal example code (optional)

/* numhelpers.c */
#include "numhelpers.h"

bool is_dec_digit(char c) {
    return c >= '0' && c <= '9';
}
/* numinfo.c */
#include <stdio.h>
#include "numhelpers.h"

int main(void) {
    char c = '7';
    printf("%c is %sa decimal digit\n", c, is_dec_digit(c) ? "" : "not ");
    return 0;
}
/* numconv.c */
#include <stdio.h>
#include "numhelpers.h"

int main(void) {
    char c = 'A';
    printf("%c is %sa decimal digit\n", c, is_dec_digit(c) ? "" : "not ");
    return 0;
}

6) Common pitfalls and fixes

  • Missing header include: If you forget #include "numhelpers.h", the compiler may assume implicit declarations or the linker may fail later. Always include the header where you use the functions.
  • Multiple definitions: Ensure each function is defined once in exactly one .c file. Reuse by including the header, not the .c file.
  • Stale objects after API changes: If you edit numhelpers.h, run make clean && make (or rely on the %.o: %.c numhelpers.h dependency so objects rebuild automatically).
  • Order of objects at link: With GCC you can usually list objects in any order when all are .o. For static libraries, order may matter—put libraries last.