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.
The compilation part is annoying, we have to add every new C file as
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
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.
<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
build : $(BIN) $(BIN) : $(OBJ) ...
System commands are working exactly the same as BASH commands. There are some variables we can use in them.
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.