Let's first of all focus on the larger picture of how GCC deals with
compilation. The overall process of compilation involves a number of
stages, shown in figure 1:
Example n: Basic structure of GCC
Figure 1 describes the compilation of a single C source file named
main.c
. It is first passed through the preprocessor, which pipes
the result to the compiler, and a parse tree is built and the file is
checked for syntactic and semantic errors. Providing the file is OK,
RTL (register transfer language) code is produced from the parse
tree and passed to the assembler, after the RTL has been turned
into an assembler source file named main.s
(it's a little more
complicated than this, but the general picture holds). The assembler
then translates the file into machine code, and main.o
is
produced. This is finally given to the link/load editor which produces
the executable a.out
.
Typically, we'd keep the source file, main.c
, and the binary,
a.out
, and the temporary files listed above (main.i
,
main.s
and main.o
) would be piped from one stage to the
next, or they'd be created as temporary files and removed when
needed. However, there are plenty of options that enable us to stop at
each phase of compilation and produce the relevant file for that stage.
Although this example illustrates the compilation of a C source file, the same process holds forward for any of the languages used with GCC: take the source file and preprocess it if necessary1; send it to the compiler which then passes the file to the assembler, which in turn is finally passed to the linker. An important need arises from this structure, to be able to make the compiler versatile - in the form of a front and back end of a compiler - which we'll now turn attention to.