Learn C

Why C, and What a Program Really Is

Compiler, Interpreter, and What Happens When You Press Run

In this lesson

  • Name the four stages between hello.c and a running program.
  • Read an error message and say which stage produced it.
  • State the trade-off between a compiler and an interpreter.

David writes eight lines to work out the diagonal of a square. He includes <math.h>, calls sqrt, presses Run, and gets this back: undefined reference to 'sqrt'. He stares at it. He did include the header.

He did not write a function called sqrt, so something else is complaining. It is not the part of the compiler that reads his code.

David is meeting the fact that "compiling" is not one program. It is four programs in a row, each with its own job and its own complaints. Once you know which one is talking, most error messages stop being frightening.

The translator and the interpreter

Imagine a technical manual written in Japanese that you need in Bangla.

Option A, the compiler. A translator takes the whole manual and works for a week. You get a complete Bangla book. The translation happens once, so reading it afterwards is as fast as reading any book. A grammar mistake in the original is caught during that week, before you read a page.

Option B, the interpreter. A human interpreter stands beside you and translates line by line as you read. You can start immediately. Every reading is slower, and a mistake on page 400 is only found when you reach page 400.

C is Option A. Python, JavaScript and PHP are closer to Option B. That one choice explains most of the differences you will meet.

The four stages of compiling a C program

When you compile hello.c, four separate programs run one after another. Each takes a file and produces a new one.

The four stages that turn hello.c into a running program hello.c your text 1. Preprocessor: pastes the headers in hello.i still C, much longer 2. Compiler: C becomes assembly hello.s assembly text 3. Assembler: assembly becomes bytes hello.o machine code, incomplete 4. Linker: joins the pieces together hello, a program you can run the C standard library where printf actually lives One command runs all four. Each stage has its own errors, and knowing which one spoke saves you an hour.
Figure 1. Four programs, four files, one command. The standard library joins at the last step.

The fourth stage is the one that caught David. The compiler was happy: <math.h> promised that a function named sqrt exists somewhere. The linker's job is to find the actual machine code for it. On many Linux systems the maths part of the library is a separate file. You ask for it by adding -lm to the command.

Note

A header like <math.h> is a promise, not the code. It says "a function called sqrt takes a double and returns a double". The code itself lives in the library. This split is why stage 1 and stage 4 can disagree.

Why this matters when you see an error

Every message below is real output from GCC. Read the table as a lookup: find the shape of your message, and it tells you who is speaking.

What the message saysStageUsual cause
fatal error: stdioo.h: No such file or directoryPreprocessorThe header name is misspelled, or not installed
error: expected ';' before 'return'CompilerA missing semicolon on the line above the one named
error: 'count' undeclared (first use in this function)CompilerA name used before it was declared, often a typo
warning: implicit declaration of function 'squareOf'CompilerA function called with no prototype above it
warning: initialization of 'int' from 'char *'CompilerA value of the wrong type put into a variable
warning: format '%s' expects argument of type 'char *'CompilerThe printf placeholder does not match the value
warning: unused variable 'total'CompilerDeclared and never used, often a half-finished edit
undefined reference to 'area'LinkerA function was declared and promised, but never written
undefined reference to 'sqrt'LinkerThe maths library was not linked; add -lm
Segmentation faultRuntimeIt built fine, then touched memory it does not own

A version note. The two middle rows, the missing prototype and the wrong type, are warnings on GCC 12 and errors on GCC 14.

Two things are worth noticing. A warning does not stop the build, and it is usually right anyway. And the last row has no compiler in it at all. That brings us to the most important sentence in this lesson.

"It compiles" is not "it is correct"

The compiler checks grammar, not meaning. It will happily build a program that gives the wrong answer every single time. You will see one in Example 3 below.

The gcc command line

gcc -std=c17 -Wall -Wextra hello.c -o hello
  • gcc is the program that runs all four stages for you.
  • -std=c17 picks the language standard. This track uses C17.
  • -Wall -Wextra turns on the useful warnings. Make this a habit on day one.
  • hello.c is your input file.
  • -o hello names the output. Leave it out and you get a file called a.out.
Example 1: the file we will follow

Seven lines. This is the file that becomes four files on its way to running.

#include <stdio.h>

int main(void)
{
    printf("Hello, C.\n");
    return 0;
}
Hello, C.

Note what is not here. There is no code for printf. Your file only says "call it". Stage 4 is where that promise is kept.

Run in Compiler
Example 2: David's program

This is the program from the first paragraph, written out in full.

#include <math.h>
#include <stdio.h>

int main(void)
{
    double side = 2.0;
    printf("The diagonal is %.4f\n", side * sqrt(2.0));
    return 0;
}
The diagonal is 2.8284

In the Playground this runs as written. On a Linux machine you may need gcc david.c -o david -lm, because there the maths code sits in a separate library file. Same source, same compiler, different linker instruction.

Run in Compiler
Example 3: it compiles, and it is wrong

No warnings. No errors. A confident, incorrect answer.

#include <stdio.h>

int main(void)
{
    int marks[2] = {7, 8};
    int average = (marks[0] + marks[1]) / 2;

    printf("Average of 7 and 8 is %d\n", average);
    return 0;
}
Average of 7 and 8 is 7

The average of 7 and 8 is 7.5. Dividing one whole number by another in C throws away the part after the point. The compiler has no opinion about this, because nothing is ungrammatical. You will fix this properly in Module 4.

Run in Compiler

Compiler and interpreter, side by side

Compiler (C, C++, Rust, Go)Interpreter (Python, JavaScript, PHP)
When translation happensOnce, before runningEvery time, while running
Speed of the finished programFast; it is native machine codeSlower; translated again and again
When errors are foundMost of them before it runsMany only when that line runs
Where the output can runTied to one kind of CPU and systemAnywhere the interpreter exists
Typical startupA compile step, then instant startInstant start, slower running
Good forOperating systems, games, embedded devices, contestsScripts, web backends, data analysis

Here is a demonstration of row three. This Python program has a spelling mistake in a function that is never called.

def never_called():
    prnt("this line is wrong")

print("Python starts running anyway")

Python prints its line and exits happily. The mistake is inside a function nobody calls, so nothing ever reads it. Put the same kind of mistake in a C file and the compiler refuses to build. It reads every line, whether or not that line will run.

Real life is a little blurrier: JIT

Java and C# do both. They first compile to a middle form called bytecode. While the program runs, a JIT (just-in-time) compiler turns the busiest parts of that bytecode into real machine code.

Modern JavaScript engines do the same thing to your browser's code. The classic split above still explains the trade-off you are choosing. Almost nothing in 2026 is purely one or the other.

What the Playground actually does

When you press Run on a Progsity Playground page, nothing magic happens. Your program is sent to a separate machine, written to a file, and compiled by GCC 12. The command is gcc -O2 -std=c17 -lm, with no -Wall and no -Wextra on it. All four stages run there, in the same order as Figure 1.

That missing -Wall matters. The Playground prints the messages GCC gives by default, so a missing prototype still shows, and a %s fed a number does not. The syntax card above is the command to run on your own machine, where you want every warning.

The compiled program then runs inside a sandbox, with a time limit and a memory limit. Its output comes back to your browser. That is the whole trick. You are using the same toolchain you would install locally, without installing it.

Where this is used

  • The Linux kernel. Built by GCC or Clang running these exact four stages over roughly 30 million lines. A full build produces tens of thousands of .o files, and the linker joins them into one image.
  • CPython. The Python interpreter is itself a compiled C program. So your future Python runs inside something that went through this pipeline first.
  • The Java Virtual Machine. HotSpot, the standard JVM, starts by interpreting bytecode and switches to JIT compilation for code that runs often. It is the blurry middle of the table above.
  • The Progsity Playground. Sends your file to the Progsity runner, compiles it with GCC 12 at -O2 -std=c17 -lm, and runs it in a sandbox with limits. Same stages, different machine.

Common mistakes

1. Fixing the line the error names, when the fault is the line above.

printf("Hi\n")
return 0;

GCC says error: expected ';' before 'return'. There is nothing wrong with the return line. The compiler only noticed the missing semicolon when it reached the next word. Always check the line above the one named.

2. Declaring a function and never writing it.

int area(int w, int h);      /* a promise */

int main(void) {
    printf("%d\n", area(3, 4));   /* nobody kept it */
    return 0;
}

The compiler is satisfied by the promise. The linker is not: undefined reference to 'area'. The fix is to write the function body, or to link the file that has it.

3. Ignoring warnings because the program ran.

int n = 5;
printf("%s\n", n);

GCC says warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int'. It builds. Then it treats the number 5 as a memory address and usually crashes. Beginners who turn on -Wall -Wextra find bugs days earlier than beginners who do not.

4. Believing a clean build means a correct program.

Example 3 above compiles with zero warnings and prints the wrong average. No stage of the pipeline checks meaning. That job is yours, and it is why the exercises in this track always name the inputs to test.

Brain teaser

Five messages, five stages. Match each message to the stage that produced it: preprocessor, compiler, assembler, linker or runtime. Then, for each one, say whether the program ever started running.

  1. fatal error: mystring.h: No such file or directory
  2. undefined reference to 'compute_total'
  3. error: 'i' undeclared (first use in this function)
  4. Segmentation fault
  5. warning: unused variable 'temp'

Three of the five stop the build. The other two both run, and only one of them finishes. For each message, ask what the speaker was holding at that moment. Your text, your assembly, or a finished program?

Exercise 1Easy

A guided terminal walk. Do this on a Linux machine, on macOS, or in WSL on Windows. Save Example 1 as hello.c, then run the four commands below one at a time.

gcc -E hello.c -o hello.i     # stop after stage 1
gcc -S hello.c -o hello.s     # stop after stage 2
gcc -c hello.c -o hello.o     # stop after stage 3
gcc hello.o -o hello          # stage 4 only

What to look for. Open hello.i: your seven lines are now more than a thousand, because the whole of stdio.h was pasted in. Your own last six lines are still at the bottom, unchanged.

Open hello.s: it is assembly, and somewhere in it you will find .ascii "Hello, C. with your message inside. hello.o is not text, so do not open it in an editor.

Check yourself. Run ./hello at the end. If it prints Hello, C. you have just done by hand what one gcc command does for you.

Exercise 2Medium

Break Example 1 on purpose, four times, and collect the real messages. Repair it after each one.

The four breaks. Misspell stdio.h as studio.h. Delete the semicolon after the printf line. Change printf to printff. Change %s into the string and pass a number instead.

What the Playground shows. The first two stop the build. The third gives a warning about the missing prototype, then a linker error. The fourth gives no message at all, because the format check lives behind -Wall, which the Playground does not pass.

Rules. Write down the exact message for each break, and the stage it came from. Do not guess the message; copy what the tool printed.

Check yourself. You should end with three different messages from at least two different stages. The fourth break is the lesson: silence is not the same as correct. On your own machine, gcc -std=c17 -Wall -Wextra gives you its message too.

Exercise 3Hard

Write, in plain English, why a program compiled on your laptop will not run on your phone. The C source would compile on both machines, so the answer is not the language.

Rules. Two paragraphs. The first must use the words "machine code" and name the stage where the source stops being portable. The second must use the table above. It must explain why a Python script can be emailed to a friend and simply work.

Check yourself. A good answer says that the source is portable and the output of stage 3 is not. If your answer blames the language rather than the stage, read Figure 1 again.

Common doubts

  • Do I need to install GCC to follow this track?

    No. Every program here runs in the Playground, which is the same compiler on a different machine. Install GCC when you start building projects with several files, which is Module 15.

  • What is the difference between GCC and Clang?

    They are two compilers for the same language. Both run these four stages and both accept the code in this track. Their error messages are worded differently, and Clang's are often easier to read.

  • Why does one missing semicolon produce ten errors?

    Because after the first confusion the compiler is reading your program wrongly, and keeps reporting what it finds. Always fix the first error and rebuild. Most of the others usually disappear.

  • Is a warning safe to ignore?

    Treat it as an error you have not been bitten by yet. Every warning in the table above describes real broken code. Professional projects build with warnings turned into errors, on purpose.

  • Is C always faster than Python?

    For the same algorithm, usually by a large margin, because there is no translation while it runs. But a good algorithm in Python beats a bad one in C every time. Choosing the algorithm matters more than choosing the language.

Key takeaways

  • Compiling is four programs in a row: preprocessor, compiler, assembler, linker.
  • Each stage has its own errors, and the message tells you which one spoke.
  • A header is a promise; the library code joins at the linker, the last stage.
  • A compiler translates once before running; an interpreter translates while running.
  • "It compiled" means the grammar is fine, and says nothing about the answer.
  • The Playground runs GCC 12 at C17 in a sandbox, without -Wall, through the same four stages.

Next you will look for C in the day you have already had. It starts with the alarm that woke you and ends with this page.

Keyboard: পরের lesson এ j, আগেরটায় k, editor খুলতে r।

Compiler, interpreter, আর Run চাপলে ভেতরে যা ঘটে | Learn C | Progsity