Post

The Magic behind .EXE generation

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!


Overview Overview


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 AddressVariable Name
0volt1
2count1
6temp1

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!

This post is licensed under CC BY 4.0 by the author.