Compiling is simply turning the source code into executable, binary file. Compiling depends heavily of the architecture of the program which should be compiled, and it can be accomplished in several ways. We will first stick to the basic compiling skills, and then continue with makefiles and other more advanced compiling techniques.
Compilation is a big process, so we define the main steps involved:
driver: It is the engine which moves all things. It starts the process of compiling. Invoked as “cc”,“gcc”..
Pre-Processor: In short, called “cpp”. It goes through all C source files and handles the pre-processor lines (#define, #include , #idfef and so on).
Compiler: Usually named “cc1”. This compiler actually translates the C source code into assembly language.
Optimizer: Of course, optimizes the code which is language-neutral.
Assembler: This tool , gets the assembly translations, which the compiler made, and converts them to machine language, placing them into object files.
Linker-Loader: This tool links all object files together into one executable. It does that in a format which is supported by the operating system. Very common format is “ELF”. The format defines the internal structure of the program: location of data, location of source code etc.
To compile a single file, let’s name it source.c, we do:
cc is a binary which is called a compiler. You might already guess that its job is to compile source files. cc is one of the many compilers out there. If you prefer GNU compiler, you could use “gcc”. On Solaris systems, “acc” is used and so on. During the compilation, the compiler might put out many error or warning messages. However, if the compilation was successful, you should get a a.out file as a result. That file is an object file. There is a way to modify the name of the compiler output, so you can use the –o (object) paramter:
$cc –o binary source.c
Here, you name the output binary and later you can simply execute it:
By this, we inform the system that we’d like to execute the file which is in our current working directory.
It is also possible, to compile more then one C source file. It can simply be done by:
$cc main.c other.c third.c –o binary
Here, we compile main.c other.c and third.c all together, and we create the binary “binary”. But, there are few things here which might get messed up. For example, if we have some variable in main.c and we’d like to use it in other.c , we’d have to define that variable as “extern”. To overcome limitations like that, we can divide the compiling process into two parts: first, we can create the object files, and then we can link those files.
$cc –c main.c
$cc –c other.c
$cc –c third.c
$cc main.o other.o third.o –o binary
The –c flag, tells the compiler that we want only object files. In the last command issued, we link all our object files into the final product, the executable file (binary). Note that linking is far more faster then actual compiling. This method is useful because we can alter which one of the parts we like, without having to recompile all parts. If we’d like to alter third.c, we can simply create a new object file of it, and link it again:
$cc –c third.c
$cc main.o other.o third.o –o binary
We do not have to recompile all three parts.
There are some compiler options which are mode advanced, but they can offer extra functionality and efficiency. First, we will discuss how we can create a debug-friendly executable file. A file like this is created by the compiler, which inserts additional information in the executable itlsef, helping to the debugger. This information is called “debug information”. It is done by:
$cc –g source.c –o binary
The –g flag tells the compiler to insert debug info into the binary. But, there is one difference. The binary will be slightly larger then a regular binary file, because now it contains additional information. But, if desired, that information can be stripped off by using the “strip” command:
The compiler can also optimize a code. It can make it faster, to occupy less memory and to be more efficient. We can use the –O flag for that:
$cc –O source.c –o binary
But, the compilation will last for longer time, because the compiler applies optimizing algorithms to the code. It is possible, however, to add numbers to the –C flag, defining the level of optimization. Higher the number is, better the optimization gets. Please note that the optimization alters the code. If you use too big optimization, is possible the code to be altered so much, so it can contain bugs.
The compiler always expresses the error, fatal messages. But , the compiler can be told to report extra warning messages, informing you where the code should be slightly modified. We can use the “-W” parameter. This parameter has different levels also, but to get all warning messages, we use “Wall”:
$cc –Wall source.c –o binary
As the programs are getting bigger and more complex, the compilation process follows. Image how hard would it be do compile a hundred, or even more source files by hand, manually, one by one. That’s why makefiles exist. They are used to pass parameters to the compiler, but the user is requred to enter just one command: make. I will give a short overview how one makefile is built, and a final example at last:
Comments: All comments in a makefile start with #.
Macros: These macros are actually reference values. We define macro as:
So we can use it later like:
which actually means:
Explicit rules: It informs makes of the file dependency, it informs make which file depends of the compilation of other files, and it also dictates the commands required to compile a specific file.
binary: source.c header.h gcc –o binary source.c header.h
These lines tell that , in order to create the file “binary”, it should use source.c and header.h files. And then, the compilation should go as:
gcc –o binary source.c header.h
Implicit rules: They are as same as explicit rules, but they do not define the commands requred to compile a file. For example:
binary: source.c header.h
will mean to execute the following:
$(CC) $(CFLAGS) –o binary source.c header.h
#This is the beggining of the makefile #These lines are ignored since they are comments #MACROS CC = gcc #We use the GNU C compiler CFLAGS = -Wall #As a flag, we use the Wall LIBS = -oobjc #Libraries to be linked with SRC = main.c employee.c employeer.c #We use two source files: source.c and other.c OBJ = main.o employee.o employeer.o #We want these object files..... #Explicit rule #We create the final executable here, main main: $(OBJ) $(CC) $(CFLAGS) –o main $(OBJ) #Implicit rules employee.o : employee.c employeer.c: employeer.c main.o: main.c employee.c employeer.c
You can very easy execute a compiled binary. If the binary name is code: