Using Makefiles to Compile Code

Makefiles are freqently used to compile code. This section will not go into detail on what steps are involved in compilation (nor does it assume any knowledge about code compilation), but rather explains how Makefiles can make compiling easier.

Often times when you’re working on a software project, your code is contained in more than one file. Let’s say we’re working on such a project and we have 3 files: “main.c,” “moreCode.c,” and “moreCode.h.” In order to run your project, you want to create an executable file by running this command:

gcc moreCode.c main.c

This creates an executable called “a.out” which you’re able to run and all is good. However, you want to tweak this command a bit by adding some flags to the gcc command and so you decide you actually want to run this command in order to generate the executable (don’t worry about what these extra flags mean, they’re beyond the scope of this course):

gcc -Wall -Wextra -Werror -std=c99 -pedantic -g -o main moreCode.c main.c

That’s quite a lot to type out everytime you want to compile your project, and it’s also inefficient if we end up recompiling something that hasn’t changed! This is the perfect example of where a Makefile would come in handy.

Let’s start the Makefile with a line indicating which shell to use and a line indicating which targets will be phony targets:

SHELL = /usr/bin/env bash
.PHONY: clean

Next we’ll define some variables that will be useful in writing our rules:

CC = gcc
CCFLAGS = -Wall -Wextra -Werror -std=c99 -pedantic -g

Here CC contains the name of the compiler and CCFLAGS contains the flags we want to use when we compile code. Now lets write some of the rules:

main: main.o moreCode.o
	$(CC) $(CCFLAGS) -o main main.o moreCode.o

	rm -f main *.o

The first rule creates an executable “main” and depends on input files “main.o” and “moreCode.o.” The recipe says, “use the variables we defined above (the compiler name and the flags) to compile “main.o” and “moreCode.o” into an executable called “main” (that’s what the -o flag is for).” Notice that the prerequisites for this rule are intermediate .o files that we don’t have! Luckily the Makefile will figure out how to create these from the .c files we’ve provided.

The second rule cleans up the output of running make.

Now if we run make, we get an executable called “main.” If we run it a second time, we will see that

make: `main` is up to date.

since we haven’t updated the code and so there is no need to recompile.

There is, however, an issue with our Makefile. We never told the Makefile that our code also depends on “moreCode.h.” Let’s add another rule to account for this:

%.o: %.c moreCode.h
	$(CC) -c -o $@ $< $(CFLAGS)

This rule says that when creating a target ending in .o, the dependencies are both the corresponding .c file and also “moreCode.h.” The recipe says, “using the compiler and the flags that we’ve specified above, create the intermediate .o file (this is what the -c flag is for) from a .c file (the $< indicates the first prerequisite, which is the .c file), and give the .o file the name of the target (this is what -o $@ is for).”


A couple of external tutorials were helpful in creating these pages. These links contain more info on using Makefiles for code compilation:

  • A Simple Makefile Tutorial
    • A tutorial that shows how to use a Makefile to compile code and shows other uses for variables in such a Makefile
  • Writing Make Files
    • A tutorial that goes into more depth on the dependencies between files when compiling C++ code
Copyright © 2014, Great Practical Ideas in Computer Science.