Antonin Hérault
Antonin Hérault

Antonin Hérault

Project organization and Makefiles (11)

Antonin Hérault's photo
Antonin Hérault
·Sep 2, 2022·

3 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

  • Include compiler flag
  • Makefiles
  • Clean the generated files

Knowing the C language is good, but having a great project organization for its C projects is better.

Let's transform this project's structure :

hello_world/
    main.c
    foo.c
    foo.h

To this :

hello_world/
    src/
        main.c
        foo.c
    include/
        foo.h

We separate the code files into the "src/" folder (means "sources"), and the header files into the "include"/ folder.

Include compiler flag

Let's compile our project : gcc src/main.c src/foo.c -o program. But, we got this error :

src/main.c:3:10: fatal error: foo.h: No such file or directory
    3 | #include "foo.h"
      |          ^~~~~~~
compilation terminated.
src/foo.c:1:10: fatal error: foo.h: No such file or directory
    1 | #include "foo.h"
      |          ^~~~~~~
compilation terminated.

That's because the "foo.h" file is not located in the "src/" folder.

We could include "include/foo.h", but this is too annoying. So, we will use a new compiler flag to easily locate the header files !

gcc src/main.c src/foo.c -I include/ -o program

Then, it works. The "foo.h" has been located into the "include/" folder.

Makefiles

The compilation part is annoying, we have to add every new C file as gcc argument.

A program called make permits to easily compile all files of our projects.

We have to create a file named Makefile at the project's root :

hello_world/
    src/
        main.c
        foo.c
    include/
        foo.h
    Makefile

A make file looks like a bash script but with some specificities :

CC = gcc # Specifies the C Compiler we are going to use
CC_FLAGS = -I include/

SRC = $(wildcard src/*.c) # Creates a variable `SRC` with all the files in the "src/" folder
OBJ = $(SRC:.c=.o) # Creates a variable `OBJ` with all the C files of `SRC` with the ".o" extension instead of ".c"

BIN = program

build : $(BIN)

$(BIN) : $(OBJ)
    $(CC) $^ -o $@ $(CC_FLAGS)

src/%.o : src/%.c
    $(CC) $< -o $@ $(CC_FLAGS) -c

Run make build in your command shell.

gcc  src/foo.c -c -o src/foo.o -I include/
gcc  src/main.c -c -o src/main.o -I include/
gcc  src/foo.o src/main.o -o program -I include/

All C files were compiled into object files, then these object files were compiled into one binary file.

I can't understand this make script... looks so weird !

In a make file, we can create variables, declare targets and run some system commands.

  • Make variables

      <ID> = <value>
    
  • Make targets and system commands

      <target> : <?prerequisites>
          <?system commands>
    

    A target can require other targets to work. The "build" target needs the "$(BIN)" target done, and the $(BIN) target needs the $(OBJ) target done.

      build : $(BIN)
    
      $(BIN) : $(OBJ)
          ...
    

    System commands are working exactly the same as BASH commands. There are some variables we can use in them.

Make variables

In target's code, we can use special variables.

  • $@ is the target's identifier
  • $< is the first prerequisite
  • $^ is all the prerequisites

This script :

program : main.c foo.c
    gcc $^ -o $@

Becomes for the make program :

program : main.c foo.c
    gcc main.c foo.c -o program

All files in a row !

We used this :

SRC = $(wildcard src/*.c)
OBJ = $(SRC:.c=.o)

src/%.o : src/%.c
    $(CC) $< -o $@ $(CC_FLAGS) -c

It permits to compile all the C files into object files by the same name. It uses the same system command.

To make this target happening, we have to require $(OBJ) as a prerequisite of another target, like there :

$(BIN) : $(OBJ)

Clean the generated files

clean :
    rm -rf src/*.o $(BIN)

Generated files should never be published with the project source code because they are not the same on every platforms.