Yi-Hsuan Tseng

Thoughts in Progress

Compile C/C++

02 Sep 2023 » C

Overview

About how to compile C/C++ using gcc/g++/clang via command line or makefile.

Process overview

latex

  • 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

Related Posts