Skip to content
VLSI Mentor

C Part I · Module 1 · Understanding C and How Programs Work

What Happens When a C Program Runs

Module 1 has followed a source file all the way to an executable. This chapter follows it one step further, into the gap Chapter 1.6 opened at the end: an executable is a file sitting on a disk, and a running program is not a file at all. Closing that gap settles three things earlier chapters deliberately left open — where the library code that never made it into your executable comes from, what runs before main and why main is therefore called rather than first, and who receives the number main returns. It is the last piece of the picture Module 1 set out to draw, and with it you should be able to account for every stage between typing a character into an editor and reading a number back from a shell.

Foundation12 min readCPart IFundamentalsExecution

Part I · Module 1 · Chapter 1.7 · What Happens When a C Program Runs

1. A File Is Not a Process

You have a file. It is a few tens of kilobytes, it sits in a directory, and if you never type its name again it will sit there indefinitely, doing nothing whatsoever. Chapter 1.6 ended by driving a wedge into that observation. Here is the wedge, all the way in.

Your executable is a file. It sits on a disk. It has a size. You can copy it, rename it, email it, and leave it untouched for a year. While it is doing that, it is not doing anything at all — it is data, in the same way your .c file is data.

A running program is not a file. It is the machine actually carrying out work, with the contents of that file as its instructions. It has a current position in those instructions, memory it is using, and an amount of time it has been running for. When it finishes, that all goes away; the file remains, unchanged, ready to be run again.

One file, run three times, is three separate running programs. They do not know about each other, and none of them changes the file.

So "running a program" is not something that happens to a file. It is something that gets set up from a file, and the setting-up is what this chapter is about.

2. What Has to Happen Before Your Code Can Start

Between your typing ./hello and your main beginning, several things have to be arranged. You do not write any of them, and you will never see most of them, but knowing they exist explains several things that are otherwise arbitrary.

Six stages. First, you ask the system to run the executable. Second, the system loads the file's contents into memory and locates any library code the executable still requires. Third, startup code supplied by the implementation runs and prepares the environment. Fourth, that startup code calls main, which is where your own code begins. Fifth, main returns a value. Sixth, that value travels back out through the startup code and becomes the program's exit status, which the shell can read.From a command to an exit status01YOU ASK FOR THE PROGRAM TO RUN./hello at a shell prompt02THE SYSTEM LOADS THE FILEcontents into memory, plus any library code still needed03STARTUP CODE RUNSsupplied by the implementation, not written by you04MAIN IS CALLEDyour code begins here — and not before05MAIN RETURNS A VALUEreturn 0, or whatever you wrote06THAT VALUE BECOMES THE EXIT STATUStravels back out; the shell can read it
Figure 1 — from a command to an exit status. Steps 2 and 3 are done for you: the system brings the file's contents into memory and finds any library code it still needs, then startup code provided by your implementation prepares the environment. Only at step 4 does your own code begin. Step 6 is the return trip — the value from step 5 travels back out the way it came, which is why the shell can read it.

Step 2 answers the question Chapter 1.6 left hanging. Your executable did not contain printf; it recorded that it needed a library that provides it. This is the moment that gets resolved. As the program is loaded, the required library is located and made available, and only then can a call to printf go anywhere. If that library cannot be found, the program fails here — before a single line of your code has run — which is a fifth kind of failure, distinct from the four in Chapter 1.6's table, and one that looks baffling until you know this step exists.

Step 3 is the one worth dwelling on.

3. main Is Called, Not First

Here is the claim Chapter 1.4 made twice and deferred twice, now stated plainly:

main is where your code begins. It is not the first thing the machine does.

Before main, code supplied by your implementation runs. It is usually called startup code, and it exists to put the machine into the state C programs are entitled to assume. It then calls main — as an ordinary call, the same shape as your call to printf in Chapter 1.4.

That single fact explains something that has been slightly odd since Chapter 1.4. Why does main return a value? Returning is what you do when someone called you. A thing that was simply the first instruction on the machine would have nobody to return to, and return 0 would be meaningless. It is meaningful precisely because main sits inside a call, and the value goes back to the caller.

What startup code actually does varies by implementation and platform, and this curriculum is not going to pretend otherwise. The portable statement is the one that matters: something runs before main, main is called, and your program's environment is prepared for you before your first statement executes.

4. What the Language Guarantees, and What Your System Arranges

This chapter is more platform-dependent than any before it, so it is worth separating clearly — using exactly the categories Chapter 1.3 gave you.

ClaimCategory
main is called when the program startsDefined by the standard
Returning from main reports that value as the program's resultDefined by the standard
0 conventionally means successA convention, honoured everywhere, not a language rule
Code runs before main to prepare the environmentEffectively universal, and the standard allows for it
How the file is loaded into memoryYour operating system's business, not C's
When and how a required library is locatedThe implementation's and system's business
What a non-zero status meansWhatever your program decides

The first two rows are the ones you can rely on anywhere C runs. The rest is true of ordinary programs on ordinary systems, and Part II eventually meets software where several of those rows have entirely different answers — code with no operating system beneath it, where the startup arrangements are something an engineer writes rather than inherits.

5. Watching the Return Trip

Chapter 1.5 showed you $? without explaining what put a number there. Now it can be traced end to end. Take the smallest program that reports a failure:

Azvya Education Pvt. Ltd.VLSI Mentor
status.c
int main(void)
{
    return 3;
}

Build and run it, then ask the shell what it got:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cc -std=c17 -Wall -Wextra -Wpedantic status.c -o status
./status
echo $?

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3

Follow the 3. You wrote it in your source. The compiler carried it into the executable. When you ran the file, the system loaded it, startup code ran and called your main, your main returned 3 to that startup code, and the startup code reported it as the program's result. The shell then read it and echo printed it.

That chain crosses every stage in Module 1. It is the smallest complete demonstration that the stages are real and connected — a value you typed came back out the far end, having passed through all of them.

One detail: returning a value from main is defined to have the same effect as ending the program with that value by other means. Chapter 12.7 covers the other means and the conventions around choosing a number. Here, the point is only that return from main is not a special ritual — it is a return, to a real caller, whose result becomes the program's report.

6. When Running Fails and Building Did Not

Chapter 1.6's table had four rows. Running gives us the fifth and sixth, and they belong to different moments:

SymptomWhenCause
A complaint about a missing library, before any outputLoading, before mainThe library the linker deferred could not be found on this machine
No output at all, or partial output, then the program stopsWhile runningYour code — Module 13's subject
The program runs and prints something wrongWhile runningYour code was valid and said the wrong thing

The first row is the one worth recognising now, because it is the only failure in Module 1 that happens after a completely successful build and before your program does anything. A build on one machine and a run on another is exactly when it appears. Nothing is wrong with your source, nothing is wrong with your executable, and the message will mention a file you have never typed — which makes sense only once you know that step 2 of Figure 1 exists.

7. What You Should Be Able to Say Now

  1. An executable is a file; a running program is the machine carrying out that file's instructions. Running one does not change the file, and the same file can be running several times at once.
  2. Before your code starts, the system loads the file's contents into memory and locates any library code the executable still requires.
  3. Code supplied by your implementation — startup code — runs before main and prepares the environment.
  4. main is called by that code. It is not the first thing the machine does.
  5. That is why main returns a value: it has a caller, and the value becomes the program's reported result.
  6. 0 meaning success is a convention, not a language rule. That main is called, and that its return value is the program's result, are language rules.
  7. A program can fail after a completely successful build and before running a single line of your code, if a required library cannot be found.

8. Exercise

Exercise 1 — separate file from process. You compile a program, then run it three times at once in three terminals. How many executables exist, how many running programs exist, and what has happened to the file? Answer in three short sentences.

Exercise 2 — explain the design. Using §3, explain why it makes sense for main to return a value, and what would be odd about return 0 if main really were the first thing the machine did.

Exercise 3 — classify the claims. Using the table in §4, say for each whether it is defined by the standard, a convention, or your system's business: (a) main runs when the program starts. (b) A program returning 0 indicates success. (c) The executable's contents are copied into memory before running. (d) A program returning 2 means a file was missing.

Exercise 4 — place the failure. A colleague builds a program successfully on their machine, copies just the executable to yours, and running it immediately fails with a message naming a library file — before any of the program's own output appears. Using Figure 1, say which step failed, and explain why recompiling your colleague's source on your machine would be a reasonable thing to try.

Exercise 5 — trace the whole module. A program's main contains only return 7;. Describe, in order, every stage that number passes through between you typing it into an editor and reading 7 from echo $?. Name at least five stages; Module 1 has taught them all.

9. Summary

  • An executable is a file; a running program is not. The file is data at rest; running is the machine carrying out its instructions, and the file is unchanged by it.
  • Loading comes first. The system brings the file's contents into memory and locates library code the executable only recorded a need for — which is where Chapter 1.6's missing printf finally arrives.
  • Something runs before main. Startup code supplied by your implementation prepares the environment, and then calls main.
  • main is called, not first — which is exactly why it returns a value, and why return 0 is communication rather than ceremony.
  • Separate what C guarantees from what your system arranges. That main is called and that its return value is the program's result are language rules. Loading, startup details and the meaning of a non-zero status are not.
  • A program can fail between a successful build and its first statement, and knowing that loading exists is what makes that failure readable rather than baffling.

Module 1 in one sentence

You can now follow a C program from text you typed, through a stated language standard, a four-stage toolchain and a loaded image, to a running process and a number handed back — and you can say, at every step, which part is the language and which part is your tools.

What you cannot yet do is make a program hold anything. Every program in this module either printed fixed text or returned a fixed number; none of them kept a value, changed it, or worked anything out. That is the whole of Module 2, and it starts where C's real subject starts: what a value is, where it lives, and what it means to give it a name.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.