Loading
0xE0Lesson 15 of 20

Writing C like a professional

The habits, tools and tricks that separate code that works today from code that keeps working.

12 min 5-question quiz 1 code exercise
By the end of this lesson you can
  • Split a program into .c and .h files with include guards
  • Use warnings, sanitizers and a debugger to find bugs early
  • Apply defensive habits: const, bounds checks, and clear ownership

You now know the core of C. This last lesson is about how experienced C programmers work, because in C, habits are what keep programs safe. None of this is extra - it is what "knowing C" means in practice.

Splitting a program into files

Once a program grows past a few hundred lines, split it into modules: a header (.h) that declares what the module offers, and a source file (.c) that implements it.

stats.h
1#ifndef STATS_H
2#define STATS_H
3
4#include <stddef.h>
5
6double mean(const double values[], size_t count);
7double maximum(const double values[], size_t count);
8
9#endif
stats.c
1#include "stats.h"
2
3double mean(const double values[], size_t count) {
4    double total = 0.0;
5    for (size_t i = 0; i < count; i++) {
6        total += values[i];
7    }
8    return count > 0 ? total / count : 0.0;
9}
10
11double maximum(const double values[], size_t count) {
12    double best = values[0];
13    for (size_t i = 1; i < count; i++) {
14        if (values[i] > best) {
15            best = values[i];
16        }
17    }
18    return best;
19}
  • main.c then writes #include "stats.h" (quotes for your own headers, angle brackets for system ones) and calls mean.
  • The #ifndef STATS_H / #define STATS_H / #endif lines are an include guard: if the header is included twice, the second copy is skipped.
  • Build with gcc -Wall -Wextra main.c stats.c -o app. Bigger projects use a Makefile (or CMake) so only changed files are recompiled.
  • Functions that are private to one file should be marked static, which hides them from other files.

Let the tools catch bugs

ToolCatchesHow
Compiler warningstype mismatches, unused variables, missing returns, = vs ==-Wall -Wextra (and -Werror to make them errors)
AddressSanitizerout-of-bounds access, use-after-free, leaks-fsanitize=address -g
UndefinedBehaviorSanitizersigned overflow, bad shifts, misaligned pointers-fsanitize=undefined
Debugger (gdb / lldb)where and why it crashed; step line by linecompile with -g, run gdb ./app
Static analyzersbugs visible without runningclang --analyze, cppcheck

Defensive habits

  • Initialize everything. Variables and pointers get a value when declared.
  • Carry sizes with data. Every array parameter comes with its length; use sizeof rather than hard-coded numbers.
  • Use bounded functions. snprintf, fgets, %99s in scanf - never an unbounded write.
  • Check return values. malloc, fopen, scanf and friends tell you when they fail; listen.
  • Make ownership obvious. For every malloc, it should be clear which function calls free. Comment it where it is not obvious.
  • Use const generously. It documents intent and turns accidental writes into compile errors.
  • Keep functions small. Small functions are easier to test, and bugs have fewer places to hide.

Where to go next

  • Read The C Programming Language (K&R). Short, precise, and still the classic.
  • Build something real: a text adventure, a to-do list that saves to a file, a tiny shell, a Game of Life, a hex dump tool.
  • Learn data structures in C: linked lists, stacks, hash tables - you now have every tool (structs, pointers, malloc) to build them.
  • Explore systems: operating system concepts, Linux system calls, or microcontrollers with an Arduino or Raspberry Pi Pico.
  • Ask and help in the discussion below each lesson. Explaining an idea to someone else is the fastest way to find the gaps in your own understanding.

Key takeaways

  • Split programs into .h (declarations, with include guards) and .c (definitions); mark file-private functions static.

  • Turn on warnings and sanitizers from day one, and learn a debugger - they find most bugs faster than you can.

  • Initialize, bound, check, and own: the defensive habits that make C programs safe.

Lesson quiz

5 questions · pass with 4 correct · up to 50 XP

Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.

Practice: write C

These run for real. Write the program, press Run tests, and read what the compiler tells you - learning to read compiler messages is half of learning C.

Exercise 1

A bounded greeting

+25 XP

Implement void make_greeting(char *out, size_t size, const char *name) so it writes Hello, <name>! into out without ever writing more than size bytes (truncate if needed). Use snprintf.

main reads a name and calls it with a small 16-byte buffer, so long names must be cut off safely. For Ada print Hello, Ada!; for Bartholomew print Hello, Bartholo (15 characters + terminator).

  • Short name
  • Long name is truncated
main.c
Loading editor…

Your code is compiled with gcc 14 (-Wall -Wextra) and run on Compiler Explorer (godbolt.org), a free public service.

Questions about this lesson

Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.

Loading posts…