Loading
0xE0Lesson 15 of 15

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

| Tool | Catches | How | | --- | --- | --- | | Compiler warnings | type mismatches, unused variables, missing returns, = vs == | -Wall -Wextra (and -Werror to make them errors) | | AddressSanitizer | out-of-bounds access, use-after-free, leaks | -fsanitize=address -g | | UndefinedBehaviorSanitizer | signed overflow, bad shifts, misaligned pointers | -fsanitize=undefined | | Debugger (gdb / lldb) | where and why it crashed; step line by line | compile with -g, run gdb ./app | | Static analyzers | bugs visible without running | clang --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 real 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.

Sign in to ask questions, help other learners, and earn XP for taking part.

Loading posts…

Did you like the lesson? 😆👍
Consider a donation to support our work: