How to Compile and Run a C Program (Linux, Mac & Windows) — Step-by-Step Guide
Before you can run a C program, you have to compile it — translate your human-readable source code into machine code your CPU can execute. This guide walks you through that process completely: installing a compiler, understanding what the compiler actually does at each stage, running your first program on Linux, macOS, or Windows, and fixing the errors you'll almost certainly encounter along the way.
Install GCC
GCC (GNU Compiler Collection) is the most widely-used C compiler in the world. It is free, open-source, and available on every major platform. Here is how to install it on each OS.
# Update package list and install GCC
sudo apt update
sudo apt install gcc
# Verify installation
gcc --version
gcc (Ubuntu 13.2.0) 13.2.0
sudo dnf install gcc
sudo pacman -S gcc
macOS does not ship GCC by default, but Apple's Xcode Command Line Tools include
clang — a fully compatible C compiler — aliased as gcc.
# Install Xcode Command Line Tools (includes clang/gcc)
xcode-select --install
# Confirm — on macOS, gcc is an alias for clang
gcc --version
Apple clang version 15.0.0 (clang-1500.3.9.4)
# For true GCC via Homebrew (optional)
brew install gcc
Windows has two recommended options. WSL is the easiest for beginners; MinGW-w64 gives you a native Windows GCC.
# In PowerShell as Administrator:
wsl --install
# Restart, then open Ubuntu from the Start menu and run:
sudo apt update && sudo apt install gcc
# 1. Download the MinGW-w64 installer from mingw-w64.org
# 2. Run the installer and choose x86_64 architecture
# 3. Add the bin\ folder to your Windows PATH
# 4. Open Command Prompt and verify:
gcc --version
gcc (x86_64-win32-seh-rev0) 13.2.0
Write Your First C Program
Create a file called hello.c using any text editor (VS Code, Vim, Nano, Notepad++).
The .c extension tells the compiler this is C source code.
#include <stdio.h> // Standard I/O library
int main() {
printf("Hello, World!\n");
return 0; // 0 = success; OS receives this value
}
#include <stdio.h> tells the preprocessor to include the standard I/O library
(which defines printf).
int main() is the entry point — every C program starts here.
printf writes text to the terminal.
return 0 tells the OS the program finished successfully (any non-zero value signals an error).
Compile with GCC
Open your terminal, navigate to the folder containing hello.c, and run:
# Basic compile — creates an executable called 'hello'
gcc hello.c -o hello
# Recommended: add warnings and debug info
gcc -Wall -Wextra -g hello.c -o hello
Breaking down the flags:
hello.c— the input source file-o hello— name the output executablehello(without-o, GCC defaults toa.out)-Wall— enable all common warnings (highly recommended)-Wextra— enable additional warnings beyond-Wall-g— include debug symbols (needed if you usegdborvalgrind)
ls -l hello (Linux/macOS) or dir hello.exe (Windows).
Run the Executable
./hello
Hello, World!
The ./ prefix means "run the file named hello in the current directory."
It is required on Unix-like systems because the current directory is not in $PATH by default.
hello.exe
Hello, World!
REM Or just:
hello
On Windows, GCC produces a .exe file. The current directory is in PATH
by default in Command Prompt, so you can run it without a path prefix.
echo $? immediately after
your program exits. If it prints 0, your return 0 reached the OS
correctly. On Windows, use echo %ERRORLEVEL%. This is how shell scripts detect
whether a program succeeded or failed.
What the Compiler Actually Does: The 4 Stages
When you run gcc hello.c -o hello, GCC doesn't do everything in one step.
It runs four separate programs internally — each one transforming your code further toward
executable machine code. Understanding these stages helps you diagnose errors and use GCC's
advanced options correctly.
Stage 1 — Preprocessing
The preprocessor (cpp) handles all lines starting with #. It expands
#include by inserting the full contents of the header file, replaces #define
macros with their values, and evaluates #ifdef / #endif conditionals.
The output is pure C text with no preprocessor directives remaining.
gcc -E hello.c -o hello.i # Produces expanded C text
wc -l hello.i
741 hello.i # stdio.h expands to 741 lines!
Stage 2 — Compilation
The compiler proper (cc1) translates preprocessed C into
assembly language — a human-readable representation of CPU instructions specific
to the target architecture (x86-64, ARM, RISC-V, etc.).
gcc -S hello.c -o hello.s # Produces assembly (.s file)
cat hello.s # View the generated x86-64 assembly
Stage 3 — Assembly
The assembler (as) converts the assembly text into binary object code
— a .o file containing machine instructions and a symbol table, but with external
references (like printf) still unresolved.
gcc -c hello.c -o hello.o # Produces object file
file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64
Stage 4 — Linking
The linker (ld) combines your .o file with library object files
(like the C standard library libc) and resolves all unresolved symbols. The result
is a complete, standalone executable the OS can load and run.
undefined reference to 'printf'
error, it happens at the linking stage — not compilation. You've written valid C, but
the linker can't find the library that contains printf. The fix is usually to add
-lm (for math functions) or ensure the standard library is linked (GCC does this
automatically for C, but not always for custom libraries).
Useful GCC Flags You Should Know
GCC has dozens of options. These are the ones every C programmer uses regularly:
| Flag | Purpose | When to use |
|---|---|---|
| -o <name> | Set output file name | Always — avoids the default a.out |
| -Wall | Enable common warnings | Always — catches many bugs before runtime |
| -Wextra | Extra warnings beyond -Wall | Recommended for all non-trivial code |
| -Werror | Treat warnings as errors | Production builds, CI/CD pipelines |
| -g | Include debug symbols | During development; required for gdb/valgrind |
| -O2 | Optimization level 2 (fast, safe) | Release builds where speed matters |
| -O0 | No optimization | Debugging — optimized code reorders lines |
| -std=c11 | Use C11 standard | When using C11 features (threads, atomics) |
| -std=c99 | Use C99 standard | Embedded/legacy compatibility |
| -lm | Link math library | When using sqrt(), pow(), etc. |
| -E | Stop after preprocessing | Debugging macro expansions |
| -S | Stop after compilation (output assembly) | Learning / low-level optimization |
| -c | Compile only, no linking | Multi-file projects, Makefiles |
| -fsanitize=address | AddressSanitizer — detects memory errors | Testing for buffer overflows, use-after-free |
# The command most professional C developers use during development:
gcc -Wall -Wextra -g -std=c11 -fsanitize=address hello.c -o hello
Common Compilation Errors and How to Fix Them
Every C beginner encounters the same set of errors. Here are the most common ones with their exact error messages, causes, and fixes:
implicit declaration of function
#include. E.g. printf needs #include <stdio.h>, sqrt needs #include <math.h>.undefined reference to '...'
-lm: gcc prog.c -o prog -lm. For custom files, compile all .c files together.expected ';' before '}'
; after the statement on the line before the reported line (errors often point one line late).use of undeclared identifier
no such file or directory
ls *.c to confirm the file exists in the current directory.Segmentation fault (runtime)
-g -fsanitize=address, re-run, and AddressSanitizer will report the exact line.Compiling C Online (No Installation Needed)
If you cannot install GCC right now, or just want to experiment quickly, several free online C compilers let you write, compile, and run C programs directly in your browser.
Compiler Explorer
Shows live assembly output as you type. Best for understanding what the compiler does.
OnlineGDB
Full online IDE with step-through debugger. Best for beginners learning to debug.
Programiz
Clean, simple interface. Best for quick "run this snippet" checks.
Replit
Full browser IDE with file system. Best for multi-file projects without local setup.
Frequently Asked Questions
.c file, then run gcc hello.c -o hello
to compile it. Run the result with ./hello on Linux/macOS or hello.exe
on Windows. If GCC produces no output, the compilation succeeded and your executable is ready.
-o flag sets the name of the output executable. Without it, GCC names
the output a.out on Linux/macOS or a.exe on Windows — a generic
name that gets overwritten every time you compile. Always use -o yourprogramname
to give each executable a meaningful name.
.c files but only compiled one of them — compile
all of them: gcc main.c utils.c -o app; (2) you're using a math function like
sqrt() but forgot -lm; (3) you declared but never defined a function.
#include and
#define directives. (2) Compilation — translates C to assembly
language. (3) Assembly — converts assembly to binary object code (.o file).
(4) Linking — combines object files and libraries into the final executable.
GCC runs all four automatically unless you use -E, -S, or -c
to stop early.
.c source files in the GCC command: gcc main.c utils.c math.c -o app.
GCC compiles each file separately and then links them together. For larger projects, use a
Makefile to only recompile files that have changed, rather than recompiling
everything every time.
-g -fsanitize=address to detect
memory errors and valgrind for deeper analysis.