Install, Compiling & Your First Program
C is the language most other languages are built on top of, or at least built to explain themselves against. Python's interpreter is written in C. Your operating system's kernel is written in C. When people say a language is "fast" or "low-level," C is usually the yardstick. Learning it doesn't just teach you a language - it teaches you what's actually happening underneath the languages you already know.
That reputation comes with a warning label: C doesn't stop you from shooting yourself in the foot. It won't check array bounds for you, it won't garbage-collect your memory, and it will happily compile code that crashes or does something quietly wrong. That's not this phase's problem to solve (we'll get there, all the way through to Phase 14: Undefined Behavior & Common Footguns) - but it's worth naming up front, because it explains why C feels different from languages you may have tried before. You are closer to the machine here, on purpose.
This phase gets you set up: a working compiler, a real understanding of what "compiling" means, and one program you wrote, built, and ran yourself.
The mental model: what a compiler actually does
What it actually is. C is a compiled language. That means the code you write in a .c file isn't
what runs - it gets translated, ahead of time, into a separate file full of raw machine instructions that
your CPU can execute directly. The program that does this translation is called a compiler. Two
compilers are used everywhere: GCC (the GNU Compiler Collection) and Clang (built on LLVM). They're
different programs with the same job, and for everything in this guide either one works the same way.
This is a genuinely different model from languages you may already know. If you've used Python or JavaScript, you're used to an interpreter reading your source code line by line while the program runs. C has no such thing at runtime. By the time your program starts, the translation is already done and finished - there's no compiler anywhere in sight, just machine code running directly on the CPU.
Why this matters. It's the reason C programs start instantly and run at full hardware speed: there's no translation step happening while the program executes, because it already happened. It's also why a compile error stops you cold before the program ever runs at all - the compiler is reading the whole program up front and refusing to produce an executable if it doesn't make sense.
📝 Terminology. Source code is the .c file you write. Compiling turns source code into
machine code - instructions in the CPU's native language. The result is an executable (or
binary) - a file you can run directly. GCC and Clang are compilers; you'll see both names in
the wild and either is fine to learn on.
Install a compiler
Which command to run depends on your OS.
macOS. Apple ships Clang through its developer tools. Open a terminal and run:
$ xcode-select --install
A dialog will prompt you to install the "Command Line Tools." Accept it - you don't need full Xcode, just
this smaller package, and it includes clang, make, and other tools you'll use throughout this guide.
Linux. Install GCC through your distribution's package manager:
$ sudo apt install build-essential # Debian/Ubuntu
$ sudo dnf install gcc # Fedora
build-essential on Debian-based systems pulls in gcc, make, and standard headers together - the
whole toolchain in one install.
Windows. The simplest path is MSYS2, which gives you a real GCC toolchain that behaves like the Linux/macOS one (matching the terminal examples throughout this guide). Download it from msys2.org, run the installer, then from the MSYS2 terminal it opens:
$ pacman -S mingw-w64-ucrt-x86_64-gcc
Close that terminal and use the "MSYS2 UCRT64" terminal from your Start menu from now on - it's the one
with GCC on its PATH. (The alternative path, Visual Studio's cl.exe compiler, works too, but its
command-line flags differ from what this guide uses.)
Confirm it worked
One command tells you the compiler is installed and reachable:
$ gcc --version
gcc (Ubuntu 13.2.0-4ubuntu3) 13.2.0
What just happened: gcc printed its version, meaning it's installed and on your PATH - the list of
places your terminal looks for commands. If you installed Clang instead, run clang --version. Either
output is fine; what matters is you get a version number back instead of a "command not found" error.
⚠️ Gotcha. If you just installed and get command not found, close and reopen your terminal. Installers
add the compiler to your PATH, but a terminal window that was already open doesn't know that until it
restarts.
Write your first program
Create a new file called hello.c in any folder you like, with exactly this content:
int
Every line here is doing real work, so let's take them one at a time before you run anything:
#include <stdio.h>pulls in declarations for the standard input/output functions -printfamong them. C's standard library isn't built into the language itself; you ask for the pieces you need. This gets a full phase of its own in Phase 8: Header Files & the Preprocessor.int main(void)definesmain, the one function every C program must have - it's where execution begins.intis the type of valuemainhands back to the operating system when it finishes.voidhere means "takes no arguments."printf("Hello, world!\n")prints the string, and\nis an escape sequence for a newline character- it can't be typed directly inside the quotes, so
\nstands in for it.
- it can't be typed directly inside the quotes, so
return 0;hands0back to the operating system, the conventional way for a program to say "I finished successfully." Any other number signals some kind of failure - your shell can check this after the program exits.
Compile it
$ gcc hello.c -o hello
What just happened: Nothing printed - and that's a good sign. gcc read hello.c, translated it to
machine code, and wrote the result to a file named hello (-o hello means "output to this filename"; if
you omit -o, GCC defaults to a less friendly a.out). Silence means the compiler had nothing to
complain about.
Look at what appeared in your folder:
$ ls
hello hello.c
hello is a new file - your executable. It's not text anymore; it's machine code, meaningless if you open
it in an editor, but exactly what your CPU needs to run the program directly.
Run it
$ ./hello
Hello, world!
What just happened: ./hello tells your shell "run the executable named hello sitting right here in
this folder" (the ./ is necessary on Linux/macOS - unlike some systems, they don't automatically look in
the current folder for commands, as a deliberate safety measure). Your CPU executed the machine code
directly - no compiler involved at this step at all, because compiling already happened.
See it catch a mistake
Compiling isn't just a formality - it's the compiler reading your entire program and refusing to produce
an executable if something's structurally wrong. Try deleting the semicolon after the printf line and
compiling again:
int
$ gcc hello.c -o hello
hello.c: In function 'main':
hello.c:5:5: error: expected ';' before 'return'
5 | return 0;
| ^~~~~~
| ;
What just happened: No executable was written this time - the compiler caught the missing semicolon and
told you exactly where it expected one, pointing at the line right after the mistake (C error messages
often point slightly after the actual problem, since the compiler doesn't realize something's missing
until it hits the next token). Clang words this a little differently and points at the end of the broken
line itself (error: expected ';' after expression, with the caret right after the )) rather than the
next line, but either way the reported spot is a signpost to look near, not always the exact character.
This is worth internalizing early: a compile error means the compiler refused to guess what you meant. Put
the semicolon back before moving on.
Two commands you'll use constantly
gcc file.c -o name- compilefile.cinto an executable calledname. You'll type a version of this every time you change your code../name- run the executable you just built. On Windows with MSYS2, the same commands work unchanged in the UCRT64 terminal.
There's no equivalent to cargo run or python file.py here that compiles and runs in one step - in C,
compiling and running are always two separate, explicit commands. That separation is the whole model: build
the machine code once, then execute it, as many times as you like, with no compiler involved on the second
step. Once your programs grow past a single file, typing the full gcc command by hand gets old fast -
Phase 9: Build Tooling shows you how to automate it.
Recap
- C is compiled, not interpreted - your
.csource gets translated ahead of time into machine code by a compiler (GCC or Clang), producing an executable that runs directly on the CPU. - Install the compiler for your OS: Xcode Command Line Tools (macOS),
build-essential(Linux), or MSYS2 (Windows). gcc file.c -o namecompiles;./nameruns the result. Two separate steps, always.#includepulls in library declarations;mainis where every C program starts;return 0signals success to the operating system.- A compile error means the compiler found something structurally wrong and refused to guess - read it as a pointer to exactly where to look, even if it points slightly after the real mistake.
You have a working compiler and a program you wrote, built, and ran by hand. Next: what those pieces inside
main actually are - variables, C's types, and how they differ from what you may already know.
Quick check
Test yourself on the idea that separates C from languages you may already know - that compiling and running are two distinct steps, not one:
[
{
"q": "You're used to running Python with `python file.py`. Why doesn't C have an equivalent single command that runs a `.c` file directly?",
"choices": [
"C source is translated into machine code by the compiler ahead of time, so by the time the program runs there's no interpreter left to hand source code to",
"C compilers are too slow to run a program immediately after reading it",
"C programs don't have a `main` function to start from",
"gcc requires an internet connection to execute a program"
],
"answer": 0,
"explain": "An interpreter reads and runs source line by line as the program executes; C's translation already happened before the program starts, so compiling and running are always two separate, explicit commands."
},
{
"q": "You run `gcc hello.c -o hello` and nothing prints to the terminal. What does that silence mean?",
"choices": [
"The compile failed silently and no executable was written",
"The compiler succeeded - it only prints when it has something to complain about",
"The compiler is still working and you need to wait longer",
"You forgot to add a flag that makes gcc report success"
],
"answer": 1,
"explain": "gcc has nothing to say when a compile goes fine; silence is the normal, successful outcome, and the new executable file is the proof it worked."
},
{
"q": "You delete a semicolon and the compiler points its error at the line *after* the one you broke. What does that tell you about reading C compile errors?",
"choices": [
"The compiler is unreliable and often blames the wrong line",
"The compiler doesn't realize a required token is missing until it reaches the next one, so the reported line can land just past the real mistake",
"Only the last line of a file can ever contain an error",
"The error means the file wasn't saved before compiling"
],
"answer": 1,
"explain": "The compiler reads forward and only notices something's missing once it hits the next token, so treat the reported line as a pointer to look near, not always the exact spot."
}
]
← Guide overview · Phase 2: Syntax, Variables & Types →
Check your understanding 3 questions
1. You're used to running Python with `python file.py`. Why doesn't C have an equivalent single command that runs a `.c` file directly?
2. You run `gcc hello.c -o hello` and nothing prints to the terminal. What does that silence mean?
3. You delete a semicolon and the compiler points its error at the line *after* the one you broke. What does that tell you about reading C compile errors?