Session 2 — Makefile: Instructor Print Guide
Instructor material — not student-facing
Single-page print reference: all demo files and terminal commands in order.
Setup
cd ~/lecture/session2/
Demo Files
include/inout.h
#ifndef INOUT_H
#define INOUT_H
#define MAX_LINE 256
int read_array(const char *filename, int *array, int max_size);
int write_array(const char *filename, const int *array, int size);
void print_array(const int *array, int size);
#endif
include/math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
void sort_array(int *array, int size);
int array_min(const int *array, int size);
int array_max(const int *array, int size);
long array_sum(const int *array, int size);
#endif
src/inout.c
#include <stdio.h>
#include <stdlib.h>
#include "inout.h"
int read_array(const char *filename, int *array, int max_size)
{
FILE *fp = fopen(filename, "r");
if (fp == NULL)
return -1;
int count = 0;
char line[MAX_LINE];
while (count < max_size && fgets(line, sizeof(line), fp) != NULL)
{
array[count] = atoi(line);
count++;
}
fclose(fp);
return count;
}
int write_array(const char *filename, const int *array, int size)
{
FILE *fp = fopen(filename, "w");
if (fp == NULL)
return -1;
for (int i = 0; i < size; i++)
fprintf(fp, "%d\n", array[i]);
fclose(fp);
return 0;
}
void print_array(const int *array, int size)
{
printf("[");
for (int i = 0; i < size; i++)
{
if (i > 0) printf(", ");
printf("%d", array[i]);
}
printf("]\n");
}
src/math_utils.c
#include "math_utils.h"
#include <limits.h>
void sort_array(int *array, int size)
{
for (int i = 0; i < size - 1; i++)
{
int min_idx = i;
for (int j = i + 1; j < size; j++)
{
if (array[j] < array[min_idx])
min_idx = j;
}
if (min_idx != i)
{
int tmp = array[i];
array[i] = array[min_idx];
array[min_idx] = tmp;
}
}
}
int array_min(const int *array, int size)
{
int min = INT_MAX;
for (int i = 0; i < size; i++)
{
if (array[i] < min)
min = array[i];
}
return min;
}
int array_max(const int *array, int size)
{
int max = INT_MIN;
for (int i = 0; i < size; i++)
{
if (array[i] > max)
max = array[i];
}
return max;
}
long array_sum(const int *array, int size)
{
long sum = 0;
for (int i = 0; i < size; i++)
sum += array[i];
return sum;
}
src/main.c
#include <stdio.h>
#include "inout.h"
#include "math_utils.h"
#define MAX_VALUES 100
int main(void)
{
int values[MAX_VALUES];
int n = read_array("data.txt", values, MAX_VALUES);
if (n < 0)
{
fprintf(stderr, "Error: could not read data.txt\n");
return 1;
}
printf("Read %d values: ", n);
print_array(values, n);
printf("Min = %d\n", array_min(values, n));
printf("Max = %d\n", array_max(values, n));
printf("Sum = %ld\n", array_sum(values, n));
sort_array(values, n);
printf("Sorted: ");
print_array(values, n);
write_array("sorted.txt", values, n);
printf("Written to sorted.txt\n");
return 0;
}
data.txt
42
17
8
99
23
56
3
71
Makefile.v0 (empty starting point)
# Makefile for session 2 — build this from scratch during the lecture
#
# Targets to implement:
# default — build the project executable
# clean — remove build artifacts
#
# Source files:
# src/main.c (uses inout.h, math_utils.h)
# src/inout.c (uses inout.h)
# src/math_utils.c (uses math_utils.h)
2.1 — Why Make? (10 min)
# Compile manually — feel the pain
gcc -W -Wall -Wextra -c src/main.c -Iinclude
gcc -W -Wall -Wextra -c src/inout.c -Iinclude
gcc -W -Wall -Wextra -c src/math_utils.c -Iinclude
gcc main.o inout.o math_utils.o -o project -lm
# Now change one file
vim src/inout.c # small change
# Do we rebuild everything? Just inout.o? How do we know?
Reference Quake 3: 328
.cfiles = 329+ commands minimum.
2.2 — First Makefile (20 min)
cp Makefile.v0 Makefile
vim Makefile
Step 1 — Hardcoded rules:
project: main.o inout.o math_utils.o
gcc main.o inout.o math_utils.o -o project -lm
main.o: src/main.c include/inout.h include/math_utils.h
gcc -W -Wall -Wextra -Iinclude -c src/main.c
inout.o: src/inout.c include/inout.h
gcc -W -Wall -Wextra -Iinclude -c src/inout.c
math_utils.o: src/math_utils.c include/math_utils.h
gcc -W -Wall -Wextra -Iinclude -c src/math_utils.c
clean:
rm -f *.o project
make
touch src/inout.c
make # only inout.o and project are rebuilt!
make clean
Draw the dependency graph on the board.
2.3 — Variables and Automatic Variables (15 min)
Step 2 — Add variables:
TARGET = project
CC = gcc
CFLAGS = -W -Wall -Wextra -Iinclude
LDFLAGS = -lm
Step 3 — Use automatic variables:
main.o: src/main.c include/inout.h include/math_utils.h
$(CC) $(CFLAGS) -c $<
Warning
▶ Wooclap Q4
Fire after writing the rule with automatic variables on screen.
Matching: $@ / $< / $^
Variable reference: $@ = target, $< = first prerequisite, $^ = all prerequisites, $* = stem.
2.4 — Inference Rules (15 min)
Step 4 — Pattern rules:
%.o: src/%.c
$(CC) $(CFLAGS) -c $<
Warning
▶ Wooclap Q5
Show the pattern-rule Makefile on the projector. Fire before running touch.
# Demonstrate the problem
touch include/inout.h
make # nothing rebuilds! Bad.
2.5 — Automatic Dependencies (15 min)
# Show what -MMD generates
gcc -MMD -Iinclude -c src/main.c
cat main.d
# Output: main.o: src/main.c include/inout.h include/math_utils.h
Step 5 — Add -MMD and -include:
CFLAGS = -W -Wall -Wextra -Iinclude -MMD
DEPS = $(OBJ:.o=.d)
-include $(DEPS)
touch include/inout.h && make # now correctly rebuilds!
2.6 — Generic Makefile (10 min)
Final version — build live:
TARGET = project
CC = gcc
CFLAGS = -W -Wall -Wextra -Iinclude -MMD
LDFLAGS = -lm
SRC = $(wildcard src/*.c)
OBJ = $(SRC:src/%.c=%.o)
DEPS = $(OBJ:.o=.d)
default: $(TARGET)
%.o: src/%.c
$(CC) $(CFLAGS) -c $<
$(TARGET): $(OBJ)
$(CC) $(LDFLAGS) $^ -o $@
-include $(DEPS)
.PHONY: clean
clean:
rm -f *.o *.d $(TARGET)
# Add a new .c file — it's automatically picked up
touch src/newmodule.c
make
2.7 — Debug/Release Builds (5 min)
DEBUG ?= yes
ifeq ($(DEBUG),yes)
CFLAGS += -g -DDEBUG
else
CFLAGS += -O3
endif
make # debug build
make DEBUG=no # release build