A compiler is a program that translates human-readable source code into computer-executable machine code. To do this successfully, the human-readable code must comply with the syntax rules of whichever programming language it is written in. The compiler is only a program and cannot fix your code for you. If you make a mistake, you have to correct the syntax or it won't compile.
What Happens When You Compile Code?
A compiler's complexity depends on the syntax of the language and how much abstraction that programming language provides. A C compiler is much simpler than a compiler for C++ or C#.
When compiling, the compiler first reads a stream of characters from a source code file and generates a stream of lexical tokens. For example, the C++ code:
int C= (A*B)+10;
might be analyzed as these tokens:
- type "int"
- variable "C"
- variable "A"
- variable "B"
- literal "10"
The lexical output goes to the syntactical analyzer part of the compiler, which uses the rules of grammar to decide whether the input is valid or not. Unless variables A and B were previously declared and were in scope, the compiler might say:
- 'A' : undeclared identifier.
If they were declared but not initialized. the compiler issues a warning:
- local variable 'A' used without being initialized.
You should never ignore compiler warnings. They can break your code in weird and unexpected ways. Always fix compiler warnings.
One Pass or Two?
Some programming languages are written so a compiler can read the source code only once and generate the machine code. Pascal is one such language. Many compilers require at least two passes. Sometimes, it is because of forward declarations of functions or classes.
In C++, a class can be declared but not defined until later. The compiler is unable to work out how much memory the class needs until it compiles the body of the class. It must reread the source code before generating the correct machine code.
Generating Machine Code
Assuming that the compiler successfully completes the lexical and syntactical analyses, the final stage is generating machine code. This is a complicated process, especially with modern CPUs.
The speed of the compiled executable code should be as fast as possible and can vary enormously according to the quality of the generated code and how much optimization was requested.
Most compilers let you specify the amount of optimization-typically known for quick debugging compiles and full optimization for the released code.
Code Generation Is Challenging
The compiler writer faces challenges when writing a code generator. Many processors speed up processing by using
- Instruction pipelining
- Internal caches.
If all the instructions within a code loop can be held in the CPU cache, then that loop runs much faster than when the CPU has to fetch instructions from the main RAM. The CPU cache is a block of memory built into the CPU chip that is accessed much faster than data in the main RAM.
Caches and Queues
Most CPUs have a pre-fetch queue where the CPU reads instructions into the cache before executing them. If a conditional branch happens, the CPU has to reload the queue. The code should be generated to minimize this.
Many CPUs have separate parts for:
- Integer arithmetic (whole numbers)
- Floating point arithmetic (fractional numbers)
These operations can often run in parallel to increase speed.
Compilers typically generate machine code into object files that are then linked together by a linker program.