KrnlPanic's Linux Notes and Tips

Working with linux since kernel version 2.0.30

C Compiler Basics

The Make command

The make command allows you to manage large programs or groups of
programs. As you begin to write larger programs, you will notice that
re-compiling larger programs takes much longer than re-compiling short
programs. Moreover, you notice that you usually only work on a small section
of the program (such as a single function that you are debugging), and much of
the rest of the program remains unchanged.

The make program aids you in developing your large programs by
keeping track of which portions of the entire program have been changed,
compiling only those parts of the program which have changed since the last
compile.

A simple compilation

Compiling a small C program requires at least a single .c file, with .h files as appropriate. Although the command to perform this task is simply cc file.c, there are 3 steps to obtain the final executable program, as shown:

  1. Compiler stage: All C language code in the .c file is converted into a lower-level language called Assembly language; making .s files.
  2. Assembler stage: The assembly language code made by the previous
    stage is then converted into object code which are fragments of code which the computer understands directly. An object code file ends with .o.

  3. Linker stage: The final stage in compiling a program involves linking the object code to code libraries which contain certain “built-in” functions, such as printf. This stage produces an executable program, which is named a.out by default.

Dependencies

The principle by which make operates was described to you in the last section. It creates programs according to the file dependencies. For example, we now know that in order to create an object file, program.o, we require at least the file program.c. (There may be other dependencies,
such as a .h file.)

This section involves drawing what are called “dependency graphs”, which are very similar to the diagrams given in the previous section. As you become proficient using make, you probably will not need to draw these diagrams, but it is important to get a feel for what you are doing.

Dependency graphs

[Image unavailable]

This graph shown in the figure is a program which is made up of 5 source files, called data.c, data.h, io.c, io.h, and main.c. At the top is the final result, a program called project1. The lines which radiate downwards from a file are the other files which it depends on. For example, to create main.o, the three files data.h, io.h, and main.c are needed.

How dependency works

Suppose that you have gone through the process of compiling the program, and while you are testing the program, you realize that one function in io.c has a bug in it. You edit io.c to fix the bug.

The figure above shows io.c outlined in red. By going up the graph, you notice that io.o needs to be updated because io.c has changed. Similarly, because io.o has changed, project1 needs to be updated as well.

How does make do it?

The make program gets its dependency “graph” from a text file called makefile or Makefile which resides in the same directory as the source files. Make checks the modification times of the files, and whenever a file becomes “newer” than something that depends on it, (in other words, modified) it executes the compiler accordingly.

For example, the previous page explained io.c was changed. If you edit io.c, it becomes “newer” than io.o, meaning that make must run cc -c io.c to create a new object file io.o, then run cc data.o main.o io.o -o project1 to build the project1 executable.

Separate compilation

[Image unavailable]

The steps taken in creating the executable program can be divided up in to two compiler/assembler steps circled in red, and one final linker step circled in yellow. The two .o files may be created separately, but both are required at the last step to create the executable program.

You can use the -c option with cc to create the corresponding object (.o) file from a .c file. For example, typing the command:
cc -c green.c will not produce an a.out binary file, as the compiler will stop after the assembler stage, leaving you with a green.o object file.

Separate compilation steps

The three different tasks required to produce the executable program are as
follows:

  • Compile green.o: cc -c green.c
  • Compile blue.o: cc -c blue.c
  • Link the parts together: cc green.o blue.o

For example, it is important to note that in order to create the file, green.o, the two files, green.c and the header file common.h are required. Similarly, in order to create the executable program, a.out, the object files green.o and blue.o are required.

Leave a Reply