Skip to main content
C beginner Lesson 2 of 23

Setting Up a C Development Environment

Install GCC, configure VS Code, and learn to compile and run C programs with Makefiles.

Installing GCC

Before you can write and run C programs, you need a compiler — the tool that translates your C source code into an executable binary. GCC (GNU Compiler Collection) is the most widely used C compiler and the best choice for learning.

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install build-essential gdb
gcc --version   # verify installation

build-essential installs GCC, G++, make, and the standard headers. gdb is the GNU debugger — you’ll want it for tracking down bugs.

macOS

macOS ships with Clang aliased to gcc. To get real GCC via Homebrew:

xcode-select --install        # installs Apple's Clang (sufficient for learning)
brew install gcc               # optional: GNU GCC
gcc --version

For most purposes, Apple’s Clang works identically to GCC for C code.

Windows

The easiest route is MSYS2, which gives you a real GCC toolchain inside a Unix-like shell:

  1. Download the installer from msys2.org
  2. Run the installer and open the MSYS2 terminal
  3. Install the toolchain:
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gdb make
  1. Add C:\msys64\ucrt64\bin to your Windows PATH
  2. Open a new terminal and run gcc --version

Alternatively, install WSL2 (Windows Subsystem for Linux) and follow the Linux instructions — this gives you a full Linux environment.

VS Code Setup

VS Code is a lightweight editor with excellent C support through extensions. It gives you syntax highlighting, IntelliSense autocompletion, and an integrated debugger without the weight of a full IDE.

  1. Install VS Code
  2. Install the C/C++ extension by Microsoft (ms-vscode.cpptools)
  3. Optionally install C/C++ Extension Pack for additional tools

Configure IntelliSense

Create .vscode/c_cpp_properties.json in your project folder so IntelliSense knows which standard you’re targeting:

{
  "configurations": [
    {
      "name": "Linux",
      "includePath": ["${workspaceFolder}/**"],
      "defines": [],
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "c11",
      "intelliSenseMode": "linux-gcc-x64"
    }
  ],
  "version": 4
}

Configure Build Task

Create .vscode/tasks.json so you can build with Ctrl+Shift+B instead of typing the compiler command every time:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Build C file",
      "type": "shell",
      "command": "gcc",
      "args": [
        "-Wall", "-Wextra", "-std=c11", "-g",
        "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}"
      ],
      "group": { "kind": "build", "isDefault": true },
      "problemMatcher": ["$gcc"]
    }
  ]
}

Press Ctrl+Shift+B to build the currently open file.

Compiling and Running

Understanding the compiler flags is important — they control what warnings you see and what standard features are available. Good flags catch bugs early, before they become hard-to-find runtime errors.

Single File

# Basic compile
gcc hello.c -o hello

# Recommended flags for development
gcc -Wall -Wextra -Wpedantic -std=c11 -g -o hello hello.c

# Run
./hello

Key compiler flags:

FlagPurpose
-WallEnable common warnings
-WextraEnable extra warnings
-WpedanticStrict standard conformance
-std=c11Use C11 standard
-gInclude debug symbols
-O2Optimization level 2 (for release)
-fsanitize=addressEnable AddressSanitizer (detects memory errors)

Multiple Files

When your project grows beyond one file, you compile each source file to an object file (.o) and then link them together. This also makes incremental builds faster — only changed files need recompilation.

# Compile each source file to an object file, then link
gcc -Wall -std=c11 -c main.c -o main.o
gcc -Wall -std=c11 -c utils.c -o utils.o
gcc main.o utils.o -o myprogram

# Or in one step
gcc -Wall -std=c11 main.c utils.c -o myprogram

Makefile Basics

A Makefile automates compilation and tracks which files need to be rebuilt. Without it, you have to remember and type all the compiler commands yourself, and rebuild everything even when only one file changed. Here is a practical template for a multi-file C project:

# Compiler and flags
CC      = gcc
CFLAGS  = -Wall -Wextra -std=c11 -g
LDFLAGS =

# Project files
TARGET  = myprogram
SRCS    = main.c utils.c parser.c
OBJS    = $(SRCS:.c=.o)

# Default target
all: $(TARGET)

# Link object files into the executable
$(TARGET): $(OBJS)
	$(CC) $(OBJS) $(LDFLAGS) -o $(TARGET)

# Compile each .c file into a .o file
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Remove build artifacts
clean:
	rm -f $(OBJS) $(TARGET)

# Rebuild from scratch
rebuild: clean all

# Declare non-file targets
.PHONY: all clean rebuild

Run it with:

make          # builds the project
make clean    # removes object files and executable
make rebuild  # clean then build

Important: Makefile recipes must be indented with a tab, not spaces. This is the most common Makefile error for beginners.

Using the Debugger (GDB)

Compile with -g to include debug symbols — this embeds information about your source code into the binary so the debugger can show you line numbers and variable names. Without it, debugging is nearly impossible.

gcc -Wall -std=c11 -g -o myprogram main.c
gdb ./myprogram

Useful GDB commands:

(gdb) break main        # set breakpoint at main
(gdb) run               # start the program
(gdb) next              # step over (execute one line)
(gdb) step              # step into (enter function calls)
(gdb) print x           # print value of variable x
(gdb) backtrace         # show call stack
(gdb) continue          # continue until next breakpoint
(gdb) quit              # exit GDB

VS Code’s debugger provides a GUI over GDB. Create .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug C Program",
      "type": "cppdbg",
      "request": "launch",
      "program": "${fileDirname}/${fileBasenameNoExtension}",
      "args": [],
      "stopAtEntry": false,
      "cwd": "${workspaceFolder}",
      "MIMode": "gdb",
      "miDebuggerPath": "/usr/bin/gdb",
      "preLaunchTask": "Build C file"
    }
  ]
}

Press F5 to build and debug.

Checking Memory Errors

C gives you direct memory access, which means bugs like buffer overflows and use-after-free can be silent — your program appears to work correctly until it suddenly crashes or produces wrong results. These tools make memory errors visible immediately, which is why you should use them from day one.

AddressSanitizer (built into GCC/Clang):

gcc -Wall -std=c11 -fsanitize=address -fsanitize=undefined -g -o myprogram main.c
./myprogram

Valgrind (Linux only):

sudo apt install valgrind
valgrind --leak-check=full ./myprogram

Both tools will catch use-after-free bugs, buffer overflows, and memory leaks that would otherwise cause hard-to-diagnose crashes. Make them part of your normal workflow from day one.

Frequently Asked Questions

Which compiler should I use — GCC, Clang, or MSVC?
GCC is the most common choice on Linux. Clang produces better error messages and is default on macOS. MSVC is Windows-native. All three support modern C standards. GCC is recommended for learning.
Do I need an IDE or is a text editor enough?
A text editor plus the terminal is perfectly fine and actually builds better habits. VS Code with the C/C++ extension gives you IntelliSense and debugging without the overhead of a full IDE.
What is a Makefile and do I need one?
A Makefile automates compilation. For single-file programs you don't need one, but once you have multiple source files a Makefile saves significant time.