Skip to main content
Compilation Toolchain
Compilation Toolchain
IOT
1.5h

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 .c files = 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