The properly so called compiler is a really complicated piece of software. It performs many different tasks on single files. This means that no matter if your project is made of thousand source files: they will be compiled one by one.
Now let's see the common operations. First of all, it does a lexical and syntax analysis over the code. Many of the errors about missing semicolons and misspelled variables will be caught here. For functions there is a small caveat. According to C standard, if a function has not been declared, the compiler assumes that:
- it exists in some other file of your project, linked library or common library (such as libc), and
- it returns an integer value.
This will lead to warnings like this:
warning: implicit declaration of function ‘fprintf’ [-Wimplicit-function-declaration]
fprintf(stderr, "Error! Expected %d param\n", ARGS);
or this:
warning: initialization makes pointer from integer without a cast
char *token = strtok(my_string, "/");
To remove them, you just have to add the declaration of the functions you are using, by including the right header file. Do not underestimate these warnings: they can lead to unexpected situations.
Other things that the compiler checks:
- types of variables in assignments;
- type and number of parameters of called functions must match the corresponding prototypes (usually declared in header files included by the preprocessor);
- const variables and parameters have to remain untouched.
Another important task that is performed is the optimization. This task can improve your code mainly in two ways: increasing the speed or reducing the size of the final output. There are many things that can be done to achieve one of these goals. GCC, for example, has lots of options to let you do a fine tuning but, in the end, the more used are -O2
(to optimize for speed) and -Os
(to optimize for size).
Eventually, the compiler produces a binary file (often called object file) full of references to unknown things. To make those things known is the job of the linker.