⚙ Arch Linux · C Development

C DEV
ENVIRONMENT

// terminal-first · progressive · powerful · yours

C-Phase-1 Journal
Overall Progress
0%
Why C?

Before writing a single line, understand what you are learning and why it matters. C is not just a language — it is the foundation of the entire computing world as it exists today.

50+
Years in active production use
#1
Language of operating system kernels
~90%
Of embedded systems written in C or C++
0
Layers between your code and the hardware

What C actually is

C is a general-purpose, procedural programming language created at Bell Labs between 1969 and 1973 by Dennis Ritchie, originally to write the Unix operating system. It was designed with one primary goal: give programmers direct, efficient control over hardware while being portable across different machines. Where assembly language speaks to one specific CPU, C speaks to all of them — but still maps almost directly to machine instructions with no hidden overhead.

C has no garbage collector, no runtime virtual machine, no automatic memory management, no built-in bounds checking. You get memory, you manage it, you free it. The computer does exactly what you tell it — no more, no less. This is both C's greatest power and its greatest danger.

Where C runs today

The Linux kernel — the operating system your Arch installation runs on — is written almost entirely in C. The GNU C Library (glibc) that your programs link against is C. CPython, the reference implementation of Python, is C. The V8 JavaScript engine inside Node.js and Chrome was built on C++ foundations with a C interface. SQLite, the most widely deployed database in the world, is pure C. OpenSSL, which secures most of the internet's encrypted traffic, is C. The firmware running on your SSD, your router, your keyboard microcontroller — all C.

When you write a Python script, your code runs inside a C program. When you use a Linux command like ls or grep, you are running a C binary that makes system calls to a C kernel. Understanding C means understanding the layer beneath everything else you will ever use.

Why a systems programmer must know C

Systems programming means writing software that is close to the hardware — operating systems, device drivers, embedded firmware, network stacks, databases, compilers, virtual machines. At this level, you cannot afford the overhead of a managed runtime. You cannot afford unpredictable garbage collection pauses. You need to know exactly how much memory you are using, exactly when it is allocated and freed, exactly what your code compiles to. C gives you this precision. No other language at this level of abstraction comes close to its combination of portability, performance, and ecosystem.

Beyond the practical: learning C teaches you how programs actually work. You will understand the stack, the heap, pointers, memory layout, calling conventions, system calls — things that higher-level programmers work with daily but rarely understand at the level that lets them debug truly hard problems. A systems programmer who knows C can read kernel code, understand security vulnerabilities, write efficient algorithms, and reason about performance at a level that is simply inaccessible without this foundation.


C vs everything else — honestly

LanguageRelationship to CWhen to use instead
C++C with classes, templates, and RAII. Almost a superset. Shares the same low-level model.When you need OOP, templates, or STL. Learn C first — C++ makes more sense after.
RustSystems language designed to replace C with compile-time memory safety. Borrows C's mental model but enforces it.New systems projects where memory safety is critical. C knowledge makes Rust vastly easier to learn.
PythonRuns on CPython, a C program. High-level abstractions hide the machine entirely.Scripting, data science, rapid prototyping. Never for systems work.
GoCompiled, garbage collected, designed for networked services. Borrows C's syntax, discards its power.Network services, CLIs, DevOps tooling. Not for bare-metal.
AssemblyOne level below C. C compiles to assembly. Reading assembly is a skill C teaches you naturally.Specific hotpaths, bootloaders, very specific hardware initialization. Rarely the full program.
The path: Learn C deeply → understand the machine → learn Rust to write safe systems code → come back to C to read and contribute to the existing world of systems software. C is not going anywhere. The Linux kernel will be C for decades.

What this environment gives you

This setup — gcc, gdb, clangd, NvChad, Arch Linux — is a professional systems programming environment. gcc is the same compiler used to build the Linux kernel. gdb is what kernel developers use to debug. clangd gives you real-time static analysis that catches bugs before you compile. Arch Linux gives you a minimal, transparent system where you understand every installed package. There is no IDE magic hiding the underlying process. You will understand exactly what happens when you type gcc -o prog prog.c.

Core Toolchain

Everything you need from the Arch repos. Each tool exists for a specific reason — understanding why you are installing it matters as much as installing it.

Update System First

Arch uses a rolling release model — there is no version to upgrade to, just a continuous stream of updates. Always sync the package database and upgrade before installing anything to avoid dependency conflicts.

bash
$ sudo pacman -Syu

Install the Core Toolchain

base-devel is a package group containing gcc, make, binutils (the linker, assembler, and object inspection tools), and other build essentials. Install it as a group — every tool in it is something you will eventually need.

bash
$ sudo pacman -S base-devel gcc gdb clang lldb cmake ninja valgrind

Verify Everything Installed

bash
$ gcc --version
gcc (GCC) 14.x.x ...
$ gdb --version
GNU gdb (GDB) 15.x ...
$ clang --version
clang version 18.x ...
$ valgrind --version
valgrind-3.x.x

gcc — GNU C Compiler

The primary compiler. Translates C source into machine code through 4 stages: preprocessing, compilation, assembly, and linking. The same compiler used to build the Linux kernel. Excellent warnings with -Wall -Wextra.

primary compiler
🔬

clang — LLVM C Compiler

Alternative compiler from the LLVM project. More readable error messages than gcc, better static analysis, and ships clangd — the language server that powers your editor's IDE features. Install both.

analysis + LSP
🐛

gdb — GNU Debugger

The standard debugger for C on Linux. Set breakpoints, step through code line by line, inspect variables and memory, examine the call stack. When your program crashes, gdb tells you exactly where and why.

debugger
🔩

valgrind — Memory Checker

Runs your program in a sandboxed environment and reports every memory error: heap buffer overflows, use after free, memory leaks, reads of uninitialized memory. Slow but thorough. Use it before shipping.

memory safety
🏗️

cmake + ninja

CMake generates build files from a portable description. Ninja executes those builds fast. Together they are the modern standard for C projects with multiple source files, external dependencies, and cross-platform builds.

build system
🔗

binutils

Included in base-devel. Contains ld (linker), as (assembler), objdump (disassemble binaries), nm (list symbols), readelf (inspect ELF files). Essential for understanding what your compiler produces.

binary tools
Editor Setup

NvChad (Neovim) with clangd LSP — real-time diagnostics, autocomplete, go-to-definition, and inline errors, entirely in the terminal. No GUI required.

Install Neovim and Dependencies

Neovim needs ripgrep (fast text search) and fd (fast file finder) for its Telescope fuzzy finder. Node.js and npm are required by some LSP servers.

bash
$ sudo pacman -S neovim ripgrep fd nodejs npm

Verify clangd is Available

clangd ships with the clang package you already installed. It is the language server that powers all IDE features inside your editor — it runs in the background, analyzes your code continuously, and feeds diagnostics to Neovim over the LSP protocol.

bash
$ clangd --version
clangd version 22.x.x ...

Configure LSP in NvChad

Add clangd to your enabled servers in ~/.config/nvim/lua/configs/lspconfig.lua. The server name must be the exact string "clangd".

~/.config/nvim/lua/configs/lspconfig.lua
require("nvchad.configs.lspconfig").defaults()

local servers = { "lua_ls", "clangd", "bashls", "rust_analyzer" }
vim.lsp.enable(servers)

Give clangd a Project Root

clangd searches upward from your file for a root marker: .git, compile_commands.json, or compile_flags.txt. For simple single-file projects, drop a compile_flags.txt in your project directory so clangd knows the C standard you are using.

bash — run from your project root
$ echo "-std=c11" > compile_flags.txt
$ # clangd now knows: this is a C11 project

Verify LSP is Active

Open a .c file in Neovim and run :LspInfo. You should see clangd listed under Active Clients with Attached buffers: 1.

inside nvim — normal mode
:LspInfo

vim.lsp: Active Clients ~
- clangd (id: 1)
  - Version: clangd version 22.x.x
  - Attached buffers: 1  ← this is what you want to see

Key Mappings for C Development

KeyActionWhen to use
gdGo to definitionJump to where a function or variable is defined — even into library headers
grFind all referencesSee every place a function or variable is used across your project
KHover docsShow the type signature and documentation for whatever is under the cursor
F2Rename symbolRename a function or variable everywhere in the project simultaneously
Space+caCode actionApply a quick fix suggested by clangd — add missing include, fix type, etc.
]d / [dNext / prev errorJump between inline compiler diagnostics without leaving the file
Ctrl+fFind fileFuzzy-find any file in the project by name
Ctrl+gLive grepSearch for any text across all files in the project
What LSP gives you: clangd parses your entire project in real time. Every time you save, it re-analyzes and reports warnings and errors inline — before you compile. It catches format string mismatches, type errors, unused variables, missing includes, and undefined behavior patterns. Treat its inline warnings as mandatory. If clangd flags something, understand why before dismissing it.
Debugging

Debugging in C is not optional — it is a core skill. Every C programmer spends significant time with gdb. Learn it early and it will save you hundreds of hours.

Always Compile with Debug Symbols

The -g flag tells gcc to embed source-level debug information into the binary: line numbers, variable names, function names. Without it, gdb can only show you raw memory addresses. The -O0 flag disables optimization — optimized code reorders and eliminates instructions in ways that make stepping through code confusing and misleading.

bash
$ gcc -g -O0 -Wall -Wextra -std=c11 -o myprog myprog.c

GDB Essential Commands

These are the commands you will use in 90% of debugging sessions. The rest you can look up with help inside gdb.

gdb session
$ gdb ./myprog
(gdb) break main            # breakpoint at start of main
(gdb) break myprog.c:42     # breakpoint at line 42
(gdb) run                   # start the program
(gdb) next                  # step over — execute one line (n)
(gdb) step                  # step into — follow function calls (s)
(gdb) finish                # run until current function returns
(gdb) print my_var          # print value of variable
(gdb) print *ptr            # dereference and print pointer
(gdb) info locals           # show all local variables
(gdb) backtrace             # show call stack (bt)
(gdb) x/16xb 0x7fffff...    # examine 16 bytes of memory in hex
(gdb) continue              # resume until next breakpoint (c)
(gdb) quit                  # exit gdb

Valgrind — Full Memory Error Detection

Valgrind's Memcheck tool runs your program inside a virtual machine that intercepts every memory access. It detects heap buffer overflows, stack errors (with --tool=exp-sgcheck), use-after-free, double-free, and memory leaks. It is 10–50x slower than native execution, but it catches bugs that cause silent data corruption long before they manifest as crashes.

bash
$ valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./myprog
==1234== HEAP SUMMARY:
==1234==   in use at exit: 0 bytes in 0 blocks
==1234==   total heap usage: 3 allocs, 3 frees, 72 bytes allocated
==1234== All heap blocks were freed -- no leaks are possible ✓

AddressSanitizer — Fast Runtime Checking

ASan is a compiler-based memory error detector built into gcc and clang. It instruments your code at compile time with checks around every memory access. It is roughly 2x slower than native (much faster than Valgrind) and catches stack overflows, heap overflows, use-after-free, and use-after-return. Use it during development on every build. -fsanitize=undefined also catches integer overflow, null dereference, misaligned access, and dozens of other undefined behavior violations.

bash
$ gcc -g -O0 -fsanitize=address,undefined -std=c11 -o myprog myprog.c
$ ./myprog
==ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 4 at 0x... thread T0
    #0 0x... in my_function myprog.c:17
    #1 0x... in main myprog.c:42
# exact line number, exact error type, full stack trace
Debugging workflow: First compile with ASAN (-fsanitize=address,undefined) and run. If it catches something, fix it. Then run under Valgrind for a thorough leak check. Use gdb when you need to step through logic interactively. These three tools together catch virtually every class of memory bug C programs can produce.
Build Systems

For a single file, invoking gcc directly is fine. For real projects — multiple source files, headers, dependencies, debug and release configurations — you need a build system.

The Standard Compiler Flags

These flags should be on every compile command during development. Treat compiler warnings as errors — a warning is the compiler telling you something is wrong, not just suspicious.

FlagWhat it does
-WallEnable common warnings: unused variables, missing returns, type mismatches, format string issues
-WextraExtra warnings: unused parameters, sign comparison, missing field initializers
-WpedanticStrict ISO C conformance — warns about any gcc extension or non-standard code
-std=c11Compile as C11 (2011 standard). Use c99 or c17 if needed.
-gEmbed debug symbols for gdb
-O0No optimization — predictable, debuggable code
-O2Release optimization — use for performance testing and shipping
-fsanitize=addressAddressSanitizer — catches memory errors at runtime
-fsanitize=undefinedUndefined Behavior Sanitizer — catches UB at runtime

A Production-Ready Makefile

Make is a build tool that tracks file dependencies and only recompiles what changed. Every experienced C developer can read and write Makefiles. This one has separate debug and release targets and automatic dependency tracking.

Makefile
CC       = gcc
CFLAGS   = -Wall -Wextra -Wpedantic -std=c11
RELEASE  = -O2
DEBUG    = -g -O0 -fsanitize=address,undefined
SRC      = $(wildcard src/*.c)
OBJ      = $(SRC:.c=.o)
TARGET   = myprog

all: $(TARGET)

$(TARGET): $(OBJ)
	$(CC) $(CFLAGS) $(RELEASE) -o $@ $^

debug: CFLAGS += $(DEBUG)
debug: $(TARGET)

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

clean:
	rm -f $(OBJ) $(TARGET)

.PHONY: all debug clean

CMake for Larger Projects

CMake generates Makefiles or Ninja build files from a portable project description. Use it when your project has multiple directories, external libraries, or needs to build on different operating systems. The CMAKE_EXPORT_COMPILE_COMMANDS=ON setting generates a compile_commands.json file that clangd uses to understand your full project.

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyProject C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_executable(myprog src/main.c src/utils.c)
target_compile_options(myprog PRIVATE -Wall -Wextra -Wpedantic)
bash — build commands
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
$ cmake --build build
$ ln -s build/compile_commands.json .  # link for clangd
Daily Workflow

The tools and habits that make terminal-based C development fast and productive.

tmux — Persistent Terminal Sessions

tmux is a terminal multiplexer. It lets you split your terminal into panes (editor in one, compiler output in another, program output in a third), and it keeps sessions alive even when you close your terminal. Detach a session with Ctrl+b d and reattach later with tmux attach — your running program and all your panes are exactly where you left them.

bash
$ sudo pacman -S tmux
KeyAction
Ctrl+b %Vertical split — two panes side by side
Ctrl+b "Horizontal split — pane above and below
Ctrl+b arrowMove focus between panes
Ctrl+b cNew window (like a new tab)
Ctrl+b dDetach session — leave it running in background
Ctrl+b [Scroll mode — use arrow keys to scroll, q to exit

git — Version Control from Day One

Commit your code constantly. Every time your program compiles and produces correct output — commit. Every time you fix a bug — commit. The discipline of committing frequently means you can always go back. Never lose working code again.

bash — first-time setup
$ git config --global user.name  "Your Name"
$ git config --global user.email "you@email.com"
$ git config --global core.editor nvim
$ git init && git add . && git commit -m "initial"

Useful Shell Aliases for C

Add these to your ~/.bashrc or ~/.zshrc. The crun function compiles and immediately runs a single-file C program — useful for quick experiments.

~/.bashrc
# compile with all warnings
alias cc='gcc -Wall -Wextra -Wpedantic -std=c11'

# compile with sanitizers for debugging
alias ccd='gcc -g -O0 -fsanitize=address,undefined -std=c11'

# compile a single file and run it immediately
function crun() {
    gcc -Wall -Wextra -std=c11 -o /tmp/crun_out "$1" && /tmp/crun_out
}

# check exit code of last command
alias ec='echo "Exit: $?"'

man pages — Your Primary Reference

Every standard C library function and every Linux system call has a manual page. man 3 is for C library functions (printf, malloc, etc.). man 2 is for Linux system calls (open, read, fork, etc.). Reading man pages is a skill — learn the format and they become the fastest reference available.

bash
$ sudo pacman -S man-pages  # install if not present
$ man 3 printf              # C library: printf
$ man 3 malloc              # C library: dynamic memory
$ man 2 open                # Linux syscall: open file
$ man 2 fork                # Linux syscall: create process
Learning Roadmap

A structured path from your first hello world to writing systems-level software. Times are honest estimates for someone studying consistently — not optimistic marketing numbers.

01
Basics — Language Foundations
4–6 weeks · K&R Chapters 1–4
active now

The core of the language: types, variables, operators, control flow, functions, arrays, and basic I/O. Your goal is to be able to read and write C programs that solve algorithmic problems, understand what every line compiles to, and be comfortable with the compiler's warning output.

What to build:

temperature converter character counter word frequency counter basic calculator fibonacci sequence simple sorting algorithms
  • Understand all basic types: int, char, float, double, long, short and their sizes
  • Write functions with correct prototypes and understand the call stack
  • Use arrays and understand that array names are pointers to their first element
  • Read and write files using fopen, fread, fwrite, fclose
  • Understand all escape sequences and printf format specifiers
  • Compile without any warnings using -Wall -Wextra -Wpedantic
02
Pointers and Memory
4–8 weeks · K&R Chapters 5–6
next

The part that defines C as a systems language. Pointers, pointer arithmetic, the heap, dynamic memory allocation, structs, and linked data structures. This phase takes longer than Phase 1 because the concepts are genuinely harder and the bugs are subtle. Do not rush it.

What to build:

linked list stack and queue dynamic array (like std::vector) string library functions from scratch binary search tree
  • Understand the difference between stack and heap allocation and when to use each
  • Use malloc, calloc, realloc, and free without leaks (verified by Valgrind)
  • Understand pointer arithmetic: what ptr+1 means for different types
  • Write functions that take pointers and modify values through them
  • Implement and use structs, nested structs, and structs with pointer members
  • Understand and use function pointers for callbacks and dispatch tables
03
Systems Programming
8–12 weeks · K&R Ch 7–8 + POSIX
future

Where C is most powerful: interfacing directly with the operating system. File I/O at the syscall level, process creation and management, signals, pipes, sockets, and threading. You will write programs that talk to the kernel directly. CS:APP (Bryant & O'Hallaron) is the primary reference for this phase.

What to build:

unix shell (fork/exec/wait) http server (sockets) cat/grep/wc reimplementations pipe-based data pipeline multi-threaded producer/consumer
  • Use open, read, write, close directly (not fopen/fread wrappers)
  • Create processes with fork() and exec(), collect exit codes with wait()
  • Handle Unix signals: SIGINT, SIGTERM, SIGCHLD
  • Create and connect TCP sockets for client-server communication
  • Use pthreads: create threads, use mutexes, understand race conditions
  • Read the Linux kernel source for a subsystem you have used
04
Real Projects
Ongoing — no end date
future

The only way to become a systems programmer is to write systems software. Pick a project that requires real C knowledge, commit to finishing it, and ship it. These projects will teach you more than any book — they are where you encounter real constraints, real bugs, and real design decisions.

Project ideas (increasing difficulty):

text editor (like nano) malloc() replacement TCP chat server JSON parser C interpreter ELF binary loader kernel module database engine

Resources
📖 Book — Primary Reference
K&R — The C Programming Language
Kernighan and Ritchie. Written by the creators of C. Still the definitive reference after 40+ years. Read the entire book. Do every exercise. This is the foundation everything else rests on.
read first
📖 Book — Systems Programming
CS:APP — Computer Systems: A Programmer's Perspective
Bryant & O'Hallaron. C programming + how computers actually work: assembly, memory hierarchy, linking, exceptional control flow, virtual memory, I/O, networking, concurrency. The best systems programming textbook in existence.
phase 2–3
🌐 Web Tool
Compiler Explorer — godbolt.org
Paste C code, see the assembly output in real time. Multiple compilers, multiple optimization levels, side by side. Invaluable for understanding what your code actually compiles to and how the compiler optimizes it.
use constantly
🌐 Web Tool
cdecl.org — C Declaration Decoder
Type in a C declaration like void (*signal(int, void (*)(int)))(int) and get a plain English explanation. C's declaration syntax is notoriously complex — this tool decodes it instantly.
pointer syntax
🌐 Reference
cppreference.com — C Standard Library
Complete reference for every C standard library function with detailed descriptions, parameters, return values, and examples. More readable than the official standard. Bookmark it.
daily reference
🌐 Reference
Linux man pages online
All Linux system call and C library man pages online. Use man 2 syscall in your terminal, but the web version is searchable. Essential for Phase 3.
phase 3
🌐 Source Code
Linux Kernel Source
The largest and most important C codebase in the world. Browse it on GitHub. Reading real-world C at this scale teaches style, patterns, and idioms that no tutorial covers. Start with kernel/sched/ or net/ipv4/.
advanced
📓 Personal Journal
C-Phase-1 — Your Learning Journal
Your documented learning journey — exact code, errors, discoveries, and deep explanations for every exercise. The only reference written specifically for your learning path, in your own words.
yours
Progress Tracker

Track your learning milestones. Checks are saved in your browser — they persist between sessions.

Overall Completion
0%
0 of 0 milestones complete