🔧 C Programming · Chapter 1
Why C, and how source code becomes a running program
Python, Git, SQLite, Linux and the Python interpreter itself are written in C. Learn it and you see what every other language hides from you.
Study this lesson free in the app →
The basics
C is a small, old (1972), very fast language that a compiler turns straight into machine code your CPU runs. There is no interpreter standing between your program and the hardware.
The problem it solves: you get control over exactly what the machine does — every byte of memory, every instruction. That is why operating systems, databases, embedded devices and language runtimes are written in it. The price: C does not protect you. Nothing checks your array bounds; nothing frees your memory.
The mental model: a C file is a recipe, not a program. Before anything runs, a build pipeline turns it into an executable:
hello.c → preprocess → compile → assemble → link → hello → run
Python reads your file every time it runs. C translates it once, ahead of time; after that, the .c file isn't needed at all.
Words you must know 4
- compiler (
gcc,clang) — translates C into machine code - object file (
.o) — one file's machine code, with holes for things defined elsewhere - linker
- stitches object files and libraries into one executable, filling the holes
main- the function the operating system calls first
Explained
Every language you will use sits on C. CPython is C. The numpy that makes Python fast is C underneath. When a Python program segfaults, it crashed in C.
Learning C is learning what a computer actually does when it runs your code — memory, bytes, addresses. It makes you better in every other language.
Printing a book, not reading it aloud
Two ways to get a Telugu reader through an English novel.
The interpreter way (Python): a translator sits beside the reader and translates each sentence as they read it. Every reading, the translator works again. Flexible, slow.
The compiler way (C): a publisher does it once.
- A copy-editor sees "insert the standard glossary here" and pastes the glossary in. (preprocessor —
#include) - A translator turns the whole English text into Telugu. (compiler + assembler)
- A binder stitches your chapters together with the pre-printed standard chapters everyone reuses. (linker + the C library)
- The finished book goes to any reader — no translator needed. (executable)
If the translator finds a sentence that makes no sense, that's a compile error. If the binder finds "see chapter 7" and there is no chapter 7, that's a linker error. Different people, different complaints.
The build pipeline
gcc hello.c -o hello runs all five steps in one command. You can stop it after any one of them to look inside.
Your first program, and how to build it
#include <stdio.h> // declares printf
int main(void) {
printf("Namaste, %s! You are %d.\n", "Asha", 21);
return 0; // 0 = success, told to the OS
}gcc -Wall -Wextra -o hello hello.c # build (warnings ON)
./hello # Namaste, Asha! You are 21.
echo $? # 0 ← main's return value%s and %d are placeholders: printf replaces them, in order, with the arguments after the format string. \n is a newline — C never adds one for you.
Stop the pipeline and look inside
gcc -E hello.c -o hello.i # preprocess only: hundreds of lines — stdio.h pasted in
gcc -S hello.c # compile to assembly: hello.s (readable CPU instructions)
gcc -c hello.c # assemble: hello.o (machine code, not runnable yet)
gcc hello.o -o hello # link: hello.o + C library → helloOpen hello.i once. Your 6-line file is now hundreds of lines long, and almost all of it is declarations from stdio.h. That is literally what #include does: copy-paste.
Set up in five minutes: Linux → sudo apt install gcc. macOS → xcode-select --install (gives you clang, also called gcc). Windows → install WSL and use the Linux line. No install at all → any online C compiler works for every lesson here.
Compile errors vs linker errors
They come from different stages, so they mean different things.
Compile error — one file is wrong on its own:
hello.c:4:5: error: expected ';' before 'return'File, line, column. Fix the line.
Linker error — every file compiled, but the pieces don't fit:
int add(int a, int b); // declared: "this exists somewhere"
int main(void) { return add(2, 3); }
// …but add() is never defined anywhereundefined reference to `add'No line number, because no single line is wrong. A promise was made and nobody kept it. Usual causes: forgot to compile the other .c file, misspelled the function, or forgot a library flag like -lm for math.h.
Which stage complains?
// main.c
#include <stdio.h>
double area(double r); // declared here, defined in geom.c
int main(void) {
printf("%.2f\n", area(2.0));
return 0;
}gcc main.c -o appGotchas that cost beginners hours
- Building without
-Wall -Wextra. C compiles plenty of wrong code silently. Warnings are where the bugs are. Treat every one as an error. - Typing
helloinstead of./hello. The shell doesn't look in the current folder. You get "command not found". - Editing
hello.cand running the oldhello. The executable is a snapshot. Rebuild after every change. - Forgetting
\n. Output gets buffered and appears late, or glued to your shell prompt. void main(). Not standard.mainreturnsint.- Mixing up the error types. A message with a line number means fix that line.
undefined referencemeans fix the build or a name.
Words to know
| Compiler | Translates C source into machine code (via assembly). gcc and clang are the common ones. |
|---|---|
| Preprocessor | Runs before the compiler; handles #include, #define and #if by rewriting text. |
| Object file (.o) | Machine code for one source file, with unresolved references to things defined elsewhere. |
| Linker | Combines object files and libraries into an executable, resolving each reference to an address. |
| Executable | The runnable file the build produces. It does not need the source to run. |
| Exit status | The integer main returns to the operating system; 0 means success. |
| Header file (.h) | A file of declarations (like stdio.h) that other files #include. |
In two minutes
- C compiles ahead of time to machine code; there is no interpreter at run time.
- The build has four stages: preprocess, compile, assemble, link.
#includeis literal copy-paste of a header file, done by the preprocessor.- An object file (
.o) is machine code with unresolved references; the linker fills them in. - Execution starts at
main, and its return value is the program's exit status (0 = success). - Compile errors point at a line in one file;
undefined referenceis a linker error about missing definitions. - Always build with
-Wall -Wextra— C accepts a lot of wrong code silently.
Test yourself
1. What does #include <stdio.h> actually do?
- Links the program with the standard I/O library at run time
- Copies the text of stdio.h into your file before compilation
- Imports a module the way Python's import does
- Tells the OS to load printf
Show the answer
Copies the text of stdio.h into your file before compilation — #include is a preprocessor directive: plain text substitution before the compiler sees the file. Linking with the library is a separate, later step, done by the linker.
2. Which stage produces a .o file?
- Preprocessor
- Linker
- Assembler (gcc -c)
- The OS loader
Show the answer
Assembler (gcc -c) — gcc -c stops after assembling: machine code for one file, not yet linked. The linker takes .o files in and puts an executable out.
3. Where does a C program start running?
- At the first line of the file
- At `main`
- At the first #include
- At whichever function is defined first
Show the answer
At `main` — The C runtime calls main. Code outside functions doesn't run; #include happened at build time.
4. You get undefined reference to 'sqrt' using math.h on Linux. The most likely fix?
- Add #include <math.h> again
- Add -lm to the gcc command
- Rename the function
- Add a semicolon
Show the answer
Add -lm to the gcc command — Including the header let the compiler accept sqrt. 'undefined reference' comes from the linker: the math library wasn't linked. On Linux, -lm links it.
5. You edit hello.c, then type ./hello without rebuilding. What runs?
- The new code — C recompiles automatically
- The old code — the executable is a snapshot from the last build
- Nothing — it errors
- It depends on the OS
Show the answer
The old code — the executable is a snapshot from the last build — There's no interpreter reading hello.c. The executable is the old translation until you run gcc again.
6. Your program's main ends with return 3;. What does echo $? print right after you run it?
- 0
- 3
- Nothing
- An error
Show the answer
3 — main's return value becomes the process exit status. Scripts and CI use non-zero values to detect failure.
There are 10 questions, 3 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.
Start this lesson →