The Magic behind .EXE generation
The Magic behind .EXE generation.
Build Process of C: From Source Code to Executable
In the build process of C, there are many steps involved from writing a C program to converting it into an executable form.
Many software development tools like Code::Blocks or Visual Studio Code, in the name of “making things easy”, hide many of these steps and make us believe we’re some kind of muggles and that magic generates the executable code.
Let’s strip away the illusion and explore what really happens!
1. Source Code
This is where it all begins.
- We create the definition for the
main()
function. - Include standard libraries.
- Call a few functions.
- Make some blunder mistakes… and fix them 😉
2. Preprocessing
In this step, the C source code is expanded based on preprocessor directives like #define
, #include
, etc.
- The expanded source code is stored in an intermediate file, usually with a
.i
extension. - The exact extension may vary depending on the compiler used.
3. Compilation
The preprocessed code is then passed to the compiler, which:
- Checks for warnings and syntax errors.
- If error-free, translates the C code into assembly language.
📄 Output: Assembly file (.s
or .asm
)
Example: Assembly code for Intel Pentium will not run on a Snapdragon 888 processor.
4. Assembling
The assembler converts the .asm
file into an object file (.o
or .obj
).
Object file contains:
- Text Section: Machine language instructions.
- Data Section: Global variables with initial values.
- BSS Section: Uninitialized global/static variables.
- Symbol Table: Metadata like names, types, addresses of variables and functions.
Question: If the object file contains machine code, why can’t we run it directly?
Answer: Because it doesn’t yet know where external symbols like printf()
are — those exist in other object files or libraries.
5. Linking
The linker comes in now to do the following:
- Combines all object files into a single file.
- Resolves references to external symbols (
printf
, etc.). - Adjusts memory addresses of all variables and functions across files.
Example:
Start Address | Variable Name |
---|---|
0 | volt1 |
2 | count1 |
6 | temp1 |
6. Executable File (.exe / ELF)
The final output is an executable file containing:
- All machine code in the text section.
- All initialized and uninitialized variables in data and BSS sections.
Execution:
- Loaded into RAM by the Program Loader.
- Since addresses are relative, the program can run from any memory location.
- Execution starts from the first instruction in the code section.
Final Note: Platform Differences
- Windows uses Portable Executable (PE) format.
- Linux uses Executable and Linkable Format (ELF).
So, an executable compiled on Windows won’t run on Linux and vice versa — they are fundamentally different formats!