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.cfor numeric helpers). - Rebuild speed: only recompile files that changed.
- Reuse: share common utilities across multiple programs (
numinfo,numconvboth usenumhelpers).
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:
3) Compile and link by hand (per the notes)¶
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.
4) Use a Makefile (recommended)¶
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.hand definitions innumhelpers.c. Any.cusing 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
.cfile. Reuse by including the header, not the.cfile. - Stale objects after API changes: If you edit
numhelpers.h, runmake clean && make(or rely on the%.o: %.c numhelpers.hdependency 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.