We can generally define three levels of abstraction from the underlying hardware when we develop embedded programs:

  • Machine Code 
  • Assembly Language 
  • High Level Language

Machine code is the native language of the microprocessors (but it is not used for directly writing programs). So no matter at what level of abstraction we decide to write our embedded program we must at the end produce a machine code that can be executed by the chosen microprocessor.  The transformation of the high level language (C, C++) source code to an executable file is referred to as build process and can be divided into three stages:

  1. Preprocessing (performed by the Preprocessor)
  2. Compilation (performed by the Compiler)
  3. Linking (performed by the Linker)

Fig.1 Build Process of C programs

Preprocessing

The preprocessing consist of  preliminary operations on the source code before the compiler sees it. All transformations are lexical and the preprocessor performs them based on specific instructions called directives that begin with the #(hash) symbol. They can be used to instruct the preprocessor to skip part of a file, include another file, or define a constant or a macro. Some of the most common directives are #define, #include, #ifdef, #if, #else etc. The output of the preprocessor is a text.

Example:

#include <stdio.h>
#define UP_LIM 1000
int var_1 = UP_LIM; //UP_LIM will be substituted with 1000 by the preprocessor
void main()
{ 
}

Compilation

The compilation takes the preprocessed source code and generates a relocatable object file. This file contains machine code and additional control information that is used by the linking process to generate an executable object file.

When we talk about compilation it should be noted that there are no strict requirement what the C code compiles into or if there are any intermediate outputs before the object file (machine code). There can be compilation that produces machine code directly from the preprocessed C code, and there can be compilation that outputs assembly language that is then passed through the assembly process (conversion of assembly language into machine code). The latter one is the most common in the embedded software development flow. As the assembly language deals more closely with the real hardware, learning some of its basic constructs and principles will give you a deeper understanding of the embedded C programming.

Linking

The linking process links (bundles) together all of the different object files into a single executable object file that can be loaded into memory and executed. The object files that are linked are usually the output of the compiler and additional object files that may have been precompiled as part of some library used in the project. The linking process allows us to break our program into separate source files that are in turn smaller, managable and easier to maintain as they can be compiled separately and then merged by the linker.

Example:

If we use printf function (part of standard C library) in our code, the linker will use the already precompiled object file containing the function and merge it with the other object files.