When we write a simple C program like the following:
#incldue <stdio.h>
int main () {
printf("Hello World!\n");
return 0;
}When we compile it and run it with the following commands:
// Compilation
gcc hello-world.c -o hello-world
// Execute
./hello-worldWhen we execute it, we might assume that our function main() is the first piece of code to execute. It should prints тАЬHello World!тАЭ to the screen, and then exists.
However behind the scenes a lot of thing happened before executing the main() function. There are actually a few special pieces of code that run before or after main(), preparing the environment for the program. These pieces of code are part of the the C Runtime (CRT) startup objects. Among them you may have encountered files like crt0.o, crt1.o, crti.o and crtn.o.
Consider it with the a real-life example, Imagine you are throwing a party before guests arrive main(), you need to set up chairs, snacks, and music. The crt files are like invisible helpers that do this setup and cleanup for us.
We can call C Runtime as the hidden code that runs before and after the
main()function.
What We Think Happens
- The compiler translates
main()into machine code. - The linker bundles necessary libraries and creates an executable.
- The operating system loads the executable into memory and starts executing
main().
While this explanation makes sense at a high level, it completely ignores the runtime initialization that must happen before main() is ever called.
What Actually Happens
Before main() functions runs, the operating system hands control over to a C Runtime startup file that sets up the execution environment. This is where crt0, crt1, crti, and crtn comes into play.
The Role of the C Runtime (CRT)
The CRT (C Runtime) is responsible for:
- Setting up the memory environments (stack, heap, BSS)
- Initializing global variables and static data.
- Handling program arguments (
argc,argv, andenvp) - Calling global constructors (for C++) and destructors
- Preparing the environment for
main() - Exiting the program cleanly after
main()returns.
Compilation and Linking
When we link the program, the linker automatically pulls in startup object files from the standard C library (libc, glibc or musl) or from the compiler toolchain and links with our program. This happens behind the scenes unless we explicitly disable it with compiler flag like -nostartupfiles.
When we compile our program:
gcc hello.c -o helloWhat actually happens is:
gcccompileshello.cinto an object file (hello.o).gccautomatically links it with startup files.- The linker resolves all dependencies and creates an executable.
To see this in action, we can inspect the linked objects:
gcc -v hello.c -o helloThis will show that the linker includes startup object files before and after hello.o in the final executabl.
CRT0
Purpose: Prepares the program's execution environment (memory, stack, global variables etc.).
Think of it as the stage crew that sets up lights, props, and seating before the play (main()) begins.
What Does crt0 do?
1 Set Up the Stack
Programs need memory for local variables and function calls. So it initializes the stack pointer (ESP/RSP on x86/64).
; Example (x86 Assembly):
movl $stack_top, %esp ; Set stack pointer to the top of the stack2 Initialize the .bss Section
The .bss section, the memory for uninitialized global variables (e.g., int global_var;).
These variables must be set to 0 before use.
; Zero the .bss section
movl $__bss_start, %edi ; Start address of .bss
movl $__bss_end, %ecx ; End address of .bss
subl %edi, %ecx ; Calculate size of .bss
xorl %eax, %eax ; Fill with zeros
rep stosb ; Repeat: store EAX (0) into [EDI++]3 Copy Initialized Data to RAM
Global variables initialized with values (e.g., int x = 7')
These values are stored in ROM (Read-Only Memory) and need to be copied to RAM.
; Copy .data section from ROM to RAM
movl $__data_start_rom, %esi ; Source (ROM)
movl $__data_start_ram, %edi ; Destination (RAM)
movl $__data_size, %ecx ; Size of the section
rep movsb ; Copy byte by byte4 Prepare Command-Line Arguments
Passes arguments from the OS to main().
Which are main(int argc, char **argv)
; Example (Linux x86):
pop %eax ; Get argc (number of arguments)
pop %ebx ; Get argv (pointer to arguments)5 Call main()
Transfers control to the program.
call main ; Jump to the main() function6 Handle Program Exit
Clean up after main() returns (e.g., call `exit() or return to the OS).
; Example (Linux x86 exit syscall):
movl $1, %eax ; Syscall number for exit
movl $0, %ebx ; Exit code (0 = success)
int $0x80 ; Trigger the syscall
Leave a comment
Your email address will not be published. Required fields are marked *


