Menu

How to Compile and Run a C Program (gcc, Step by Step)

Turn a .c file into a running program: the gcc command, what preprocessing, compiling, and linking actually do, the flags worth using from day one, and how to read the errors when it fails.

This page includes runnable editors - edit, run, and see output instantly.

C is a compiled language: nothing runs until a compiler has translated your source into machine code for your specific CPU and operating system. That step is one command, but knowing what it does turns most error messages from mysterious into obvious.

The Two Commands

Save this as hello.c:

#include <stdio.h>

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

Then, in the same folder:

gcc hello.c -o hello
./hello

The first command compiles. The second runs the result. On Windows the run command is hello in Command Prompt or .\hello in PowerShell - there is no ./ prefix, because Windows searches the current directory by default and Unix shells deliberately do not.

If you leave out -o hello, gcc names the output a.out (or a.exe), which is why older tutorials end with ./a.out. Always name your output; it costs four characters and saves confusion.

Here is the same program in the browser editor, which does both steps for you:

What "Compiling" Actually Does

gcc hello.c -o hello looks like one step. It is four, and each one can fail with its own kind of error.

1. Preprocessing. Before any C is compiled, the preprocessor handles every line starting with #. #include <stdio.h> is literally replaced by the contents of that header file; #define macros are expanded; #ifdef blocks are kept or deleted. The output is one big C file with no # lines left. You can see it:

gcc -E hello.c

That prints hundreds of lines - almost all of it is stdio.h being pasted in.

2. Compiling. The preprocessed C is parsed, type-checked, and translated into assembly for your CPU. This is where syntax errors, type errors, and warnings come from.

3. Assembling. The assembly becomes an object file - machine code with placeholders where calls to other files' functions go.

gcc -c hello.c   # produces hello.o, stops before linking

4. Linking. The object files are stitched together with the C standard library, every placeholder is filled with a real address, and the result is an executable. This is where "undefined reference" errors come from.

The practical takeaway: a compile error points at a line in your source, and a link error does not, because linking happens after every line has already been accepted.

Flags Worth Using From Day One

The bare gcc file.c -o prog accepts a lot of dangerous code silently. Four flags change that.

gcc -std=c17 -Wall -Wextra -g hello.c -o hello
  • -Wall enables the common warnings. Despite the name it is not "all" warnings - it is the sensible set.
  • -Wextra adds more, including unused parameters and some comparison mistakes.
  • -std=c17 pins the language standard so your code means the same thing on every machine. Use -std=c99 if you are following older material.
  • -g keeps debugging information, so gdb or lldb can show your actual source lines when something crashes.

Two more you will want eventually:

  • -O2 turns on optimization for release builds. Leave it off while learning: optimized code is harder to debug, and warnings sometimes shift.
  • -fsanitize=address,undefined (gcc and clang) makes the program abort with a clear message the moment it reads out of bounds or hits undefined behavior. This is the single most useful learning flag in C.

Try the warnings for yourself. This program compiles and runs, but it has two real bugs:

count is never given a value, so the program prints whatever bytes happened to be there. With -Wall the compiler says so: 'count' is used uninitialized. Warnings in C are almost never noise - treat them as errors you have not hit yet.

Compiling More Than One File

Real programs are split across files. Pass them all to gcc:

gcc -std=c17 -Wall main.c utils.c -o myprog

Or compile each separately and link at the end, which is what build systems do so that changing one file does not rebuild everything:

gcc -c main.c      # -> main.o
gcc -c utils.c     # -> utils.o
gcc main.o utils.o -o myprog

Some libraries need an explicit link flag. The math library is the one every beginner meets:

gcc calc.c -o calc -lm

Without -lm, using sqrt from math.h compiles fine and then fails at link time with undefined reference to sqrt - the declaration was in the header, but the code was in a library nobody asked for.

Reading the Errors

C's errors are terse but consistent. Three examples cover most of what you will see early on.

A missing semicolon reports on the next line, because the compiler kept reading:

hello.c:5:5: error: expected ';' before 'return'

The fix belongs on line 4, not line 5. Whenever an error points at a line that looks fine, check the line before it.

A missing header looks like a mystery about a function you clearly spelled correctly:

hello.c:4:5: warning: implicit declaration of function 'printf'

That means the compiler never saw a declaration for printf, so you forgot #include <stdio.h>. In C99 and later this is an error, not just a warning.

A link failure has no line number at all:

/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x1a): undefined reference to `helper'

The compiler believed you that helper exists somewhere; the linker looked and it does not. Either you never wrote the definition, you spelled it differently, or you forgot to pass the file that contains it to gcc.

Always fix the first error. C errors cascade - one bad line can generate twenty messages, and nineteen of them vanish when the first is fixed.

Exit Codes

return 0 from main is not decoration. It is the program's exit status, and the shell can read it:

After running, echo $? on macOS/Linux (or echo %errorlevel% on Windows) prints that number. Scripts and build tools use it to decide whether to continue. The convention is absolute: 0 is success, and non-zero is an error code of your choosing. stdlib.h defines EXIT_SUCCESS and EXIT_FAILURE if you prefer the names.

Since C99, falling off the end of main without a return is treated as return 0 - but writing it is clearer, and it is required for every other function.

Frequently Asked Questions

How do I compile and run a C program?

Save the code as hello.c, then run gcc hello.c -o hello to build it and ./hello to run it (just hello on Windows). If gcc is not found, you need to install a compiler first.

What does gcc -o do?

-o names the output file. gcc hello.c -o hello produces an executable called hello. Without -o, gcc writes to a.out (or a.exe on Windows), which is why so many old tutorials run ./a.out.

Which gcc flags should I always use?

gcc -std=c17 -Wall -Wextra -g yourfile.c -o yourprog. -Wall -Wextra turn on the warnings that catch real bugs, -std=c17 pins the language version, and -g keeps debug symbols so a debugger can show your source. Add -O2 when you want speed in a release build.

What does "undefined reference to" mean in C?

It is a linker error: the compiler accepted a call to a function, but no definition of that function was found. Common causes are a typo in the name, forgetting to compile a second .c file, or using a math function without linking the math library (-lm).

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED