Overview
About how to compile C/C++ using gcc/g++/clang via command line or makefile.
Process overview
- preprocessor (-E): handle
#include
,#define
, strips out comments- find the library function included and copy and paste to your progam.
- Ex:
#include "my_func.c"
the double quote means to search in the current directory to find this specific file. The proprocessor would find that piece of code and copy the contents over.
- compiler(-S): translate C to assembly
- assembler(-c): translate assembly to object file. (.o)
- linker: brings together object files to produce executable.
By specifying the flag (-E/-S/-c), it tells clang/gcc/g++ where to stop. (default: build an executable)
Ex: clang -c geom.c -o goem.o
: builds object file “geom.o”
Example 1
For instance, now we have
- main.c
- message.cpp
- message.h
We want to compile the whole program into an executable.
# for c++
g++ main.cpp message.cpp
# for c
gcc main.c message.c
The default output name would be a.out
. To specify the name of the output file, use the -o flag
ex:
# result output file (here, an executbable): "main"
g++ -o main main.cpp message.cpp
Example 2
Say there are now two files.
- main.c - main function, using a function from utils.c
- utils.c - many utility functions
Idea: build object files of them separately, and then bring them together to build the final executable. (i.e., link the object files)
Note that, in order to build main.o, we need to have function prototype of utility function in main.c. This can be done by including the header file. And this is also how the including header files work. We include the header files of library functions, and we don’t need the actual code, but only the prototype, of the functions during building object file. In the end, we would then link to the actual code in order to build the executable.
ex:
#include <stdio.h>
#include <utils.h> //define the function prototype
int main(){
int result = calc(0, 1000);
...
return 0;
}
Then we can start to build!
> gcc -c main.c #main.c->main.o
> gcc -c utils.c #utils.c->utils.o
> gcc main.o utils.o -o output #main.o & utils.o -> output
Automate building with makefile
It may be troublesome when we want to compile multiple source files together. Makefile thus come into rescue.
Basic syntax
Basic rules:
- makefiles are whitespace sensitive, so need to make sure the tabs are placed at correct positions.
- format
target: dependencies action
The idea is that, we would build the target based on the dependencies. Every time the dependency is changed, it would invoke the action again.
The dependencies of the source file (.c/.cpp) should also include the header files, so that if there are any changes in the header files, the object files could be rebuild.
With the same example, we can write the makefile:
output: main.o message.o
g++ main.o message.o -o output
main.o: main.cpp message.h
g++ -c main.cpp
message.o: message.cpp message.h
g++ -c message.cpp
clean:
rm *.o output
By typing make
command, we run the makefile.
make clean
the last part of the makefile is the clean command.
# remove all object files (.o) and the output
clean:
rm *.o output hello
If we add -f flag in the command rm -f
, it forces the removal and no error message would be shown if the specified files don’t exist.
Note that, you should be careful of this command, cuz it would delete files. If you accidentally add a whitespace after the star sign, like this: rm -f * .o
, it would simply delete everything in the directory.
recompile
If there is no any changes in all files, then typing ‘make’ again would do nothing (it would show that the output is up to date). But if there are changes from the last make, then it would detect which files are changed and re-execute the specific actions.
For instance, if I change something in the main.cpp (either change the content, or just simply touch main.cpp, which would create a new timestamp for this file), then it would invoke to recreate main.o.
Gist: only the necessary pieces would be recompiled
If another recipe is added?
If we add a new recipe at the botoom of makefile, when we “make” again, it would still do nothing and say that everything is up to date.
Reason: the make program starts at the top of the file and just execute the first recipe, build the first target, and then stop. So in order to let the make program finds out this new recipe, we need to say “make ${name_of_new_target}”.
ex:
output: main.o message.o
g++ main.o message.o -o output
main.o: main.cpp message.h
g++ -c main.cpp
message.o: message.cpp message.h
g++ -c message.cpp
# new recipe: hello
hello: hello.o
g++ hello.o -o hello
hello.o: hello.cpp message.h
g++ -c hello.cpp
clean:
rm *.o output hello
Then we need to say make hello
to let the make program jump to this point and build this new executable.
Convention of building multiple targets
You can write another recipe : all, and specify all the targets you want to build by default.
all: output hello
Simplify the makefile
By using variables, we can write the makefile in a more concise manner.
There are variables used by implicit rules (list), like CC
, CFLAGS
, which can be altered in the makefile.
There are also automatic variables (list)
$@
: Target names$^
: Dependency names
Example1:
CC=g++
CFLAGS=-g -Wall
all: hello output
output: main.o message.o
#gcc -g -Wall -o output main.o message.o
$(CC) $(CFLAGS) -o $@ $^
main.o: main.cpp message.h
$(CC) $(CFLAGS) -c $^ -o $@
message.o: message.cpp message.h
$(CC) $(CFLAGS) -c $^ -o $@
hello: hello.o
$(CC) $(CFLAGS) -o $@ $^
hello.o: hello.cpp message.h
$(CC) $(CFLAGS) -o $@ $^
Example2: If you are dealing with all repetitive things, (i.e., building xxx.o based on xxx.c and xxx.h), then it can be further simplified as
%.o: %.c %.h
$(CC) $(CFLAGS) -c $^
Reference
- variables used by implicit rules in gnu make - https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html
- automatic variables of gnu make - https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html