# The C Runtime?

I always thought that when we compile a C code, it *‘just works’*. No other fancy code runs to make our code function, like in the case for Java and Python. But turns out, it’s not *exactly* true. There are some work that happens behind the scenes when we run a binary written in C.

This code is called the **C Runtime (CRT)**, but don’t confuse this with the Java Runtime Environment (JRE) or the Python Runtime Environment (PRE). Unlike Java and Python code, C code directly runs on the bare-metal, but there does exist some work that needs to be done before, while and after our code runs. This is handled by the C Runtime. Let’s dive a bit deeper…

The CRT consists of four main components:

* **Startup Sequence:** Before our `main()` function even runs, the CRT performs initialization. It sets up the stack, initializes global variables, and gathers command-line arguments.
    
* **Memory Management:** It provides the logic for functions like `malloc()` and `free()`, managing the "Heap" memory, so our program can request space dynamically.
    
* **Standard Library (*libc*):** This is the bulk of the CRT. It includes the math functions, string manipulation tools, and input/output handlers (like `printf()` and `scanf()`).
    
* **The Stack and Heap:** The CRT manages the memory layout. The Stack handles local variables and function calls, while the Heap handles dynamic data.
    

Let’s dive a little bit deeper into these four tasks of the CRT.

# The Startup Sequence (The "C Bootstrap")

When we click "Run," the Operating System doesn't jump straight to our `int main()`. It jumps to an entry point provided by the CRT (often called `_start` or `mainCRTStartup`).

* **Setting the Environment:** The CRT retrieves the Environment Variables (like *PATH*) and the Command Line Arguments from the OS. It parses them into the `argc` and `argv` parameters we see in `main()`.
    
* **Initialization of Statics:** It clears the BSS segment (setting un-initialized global variables to zero) and copies initial values into the Data segment for variables like `int x = 10;`.
    
* **Constructors:** In C++ (or C with specific compiler extensions), the CRT calls "global constructors" before main starts.
    
* **The Handshake:** Finally, it calls `main()`. When `main()` finishes, the CRT takes the return value (e.g., 0) and passes it back to the OS via an `exit()` system call.
    

# Memory Management (`malloc` and `free`)

The Operating System is strict; it only gives memory to programs in large "pages" (usually 4KB). If we only need 20 bytes, asking the OS directly is incredibly slow and wasteful.

* **The Middleman:** The CRT maintains a "pool" of memory. When we call `malloc(20)`, the CRT looks at its pool, carves out 20 bytes, marks them as "in use," and gives you the pointer.
    
* **Bookkeeping:** The CRT keeps a hidden header just before our pointer that stores the size of the block. This is how `free()` knows exactly how much memory to release without us telling it the size.
    
* **Heap Expansion:** If the CRT runs out of memory in its pool, it issues a system call (like `sbrk` on Linux or `VirtualAlloc` on Windows) to ask the OS for another big chunk of RAM.
    

# The Stack vs. The Heap

These are the two primary ways a program uses RAM, and the CRT (along with the CPU) manages the boundary between them.

* **The Stack:** Every time we call a function, the CRT pushes a "Frame" onto the stack. This frame holds our local variables and the "return address" (where to go when the function ends).
    
* **The Heap:** The "pile" of memory used for data that needs to live a long time or is too big for the stack (like a high-resolution image).
    

# The Standard Library (`libc`)

This is the "toolbox" of C. It is a collection of pre-compiled code that performs common tasks that would be hard to write from scratch.

* **System Call Wrappers:** Functions like `printf()` or `fopen()` are wrappers. They take our high-level request, format it, and then perform a System Call to ask the OS kernel to actually write pixels to a screen or bits to a disk.
    
* **Portability Layer:** `libc` is the reason our code can run on both Windows and Linux. On Windows, `printf()` might call `WriteFile`, while on Linux, it calls `write`. We don't have to care; the CRT handles the translation.
    
* **Utility Functions:** These are pure logic. Things like `sqrt()`, `strlen()`, or `sort()`. They don't talk to the OS; they just provide optimized algorithms so we don't have to reinvent the wheel.
    

# The Code

Curious as to how these are managed? Checkout the [glibc repository](https://github.com/bminor/glibc/tree/master) to see these components in action. The `_start` code is under `sysdeps/x86_64/start.S`. This is in Assembly to facilitate the required low-level work. The memory allocation logic is under `malloc/malloc.c`. This is quite a complicated bit of code.

# Conclusion

So what did we learn? Even C is not about compile and run. There’s a lot more happening under the hood than what we’re doing ourselves. It’s quite interesting how far these tools have come. So next time you write a new `main()`, think of all the work happening under the hood that keeps your code running correctly.
