This is the preprocessor output generated using "cc -E helloworld.c -o helloworld.pp".

The compiler takes the fully expanded C/C++ code and generates machine specific assembly code and often introduces various optimisations and can perform complex checks for example for type mismatches and stack overflows. This is the compilter output generated using "cc -S helloworld.c -o helloworld.s"

The assembly code is now passed to the assembler which turns the assembly code into binary code (machine code) in an output file called an object file. Both cc and gcc use the same assembler by default called "as". The binary code produced by the assembler is not absolute with regards to its references to memory addresses and function calls, instead it uses labels. Many object files are expected to be generated and only when they are merged will all locations become clear and relevant. This is the assembler output generated using "as helloworld.s -o helloworld.out".

Next the linker takes several object files (assembly code compiled into binary) and resolves the labels and references between them. Often the program code is assembled and linked with pre-assembled versions of C libraries that were references in the original code. The linker is called using "ld -o helloworld helloworld.out -lc --entry main" to produce the final binary executable file. This is the file generated. Note that the test system used for compiling has some path issues as the binary is crashing:

Finally the loader is used to load the binary file into memory and execute its instructions. This example binary file is in ELF format so it contains an ELF header and data sections. The syscall execve is used to start the new process and the linker-loader ld is called as well as the libc library. The \777ELF magic word can be seen at the start of the read() of the libc library to detemine this is an ELF file also: