◐ ai-teacher

🔧 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.

  1. A copy-editor sees "insert the standard glossary here" and pastes the glossary in. (preprocessor — #include)
  2. A translator turns the whole English text into Telugu. (compiler + assembler)
  3. A binder stitches your chapters together with the pre-printed standard chapters everyone reuses. (linker + the C library)
  4. 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 → hello

Open 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 anywhere
undefined 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 app

Gotchas that cost beginners hours

Words to know

CompilerTranslates C source into machine code (via assembly). gcc and clang are the common ones.
PreprocessorRuns 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.
LinkerCombines object files and libraries into an executable, resolving each reference to an address.
ExecutableThe runnable file the build produces. It does not need the source to run.
Exit statusThe 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

Test yourself

1. What does #include <stdio.h> actually do?

  1. Links the program with the standard I/O library at run time
  2. Copies the text of stdio.h into your file before compilation
  3. Imports a module the way Python's import does
  4. 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?

  1. Preprocessor
  2. Linker
  3. Assembler (gcc -c)
  4. 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?

  1. At the first line of the file
  2. At `main`
  3. At the first #include
  4. 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?

  1. Add #include <math.h> again
  2. Add -lm to the gcc command
  3. Rename the function
  4. 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?

  1. The new code — C recompiles automatically
  2. The old code — the executable is a snapshot from the last build
  3. Nothing — it errors
  4. 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?

  1. 0
  2. 3
  3. Nothing
  4. 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.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →