Frontend Labs
⋯

Part IV — Real-world builds

Theory is done — Parts I–III gave you everything this Part uses. From here it's full, opinionated builds rather than isolated examples: a production CLI, a text editor, a Unix shell, and a bump allocator, each combining memory management, data structures, and I/O into something closer to what you'd actually ship.

24. Building production CLIs: argument parsing, config precedence, exit codes

24.1 getopt — the standard way to parse flags

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int verbose = 0;
    char *output = "out.txt";
    int opt;

    while ((opt = getopt(argc, argv, "vo:h")) != -1) {
        switch (opt) {
            case 'v': verbose = 1; break;
            case 'o': output = optarg; break;   // optarg is set by getopt for options that take a value
            case 'h':
                printf("usage: %s [-v] [-o file] [input...]\n", argv[0]);
                return 0;
            default:
                fprintf(stderr, "usage: %s [-v] [-o file] [input...]\n", argv[0]);
                return 2;   // convention: 2 for usage errors, 1 for runtime errors, 0 for success
        }
    }

    for (int i = optind; i < argc; i++) {   // optind marks where positional args start
        printf("input: %s\n", argv[i]);
    }

    printf("verbose=%d output=%s\n", verbose, output);
    return 0;
}

24.2 Config precedence — the convention many serious CLIs follow

defaults  <  config file  <  environment variables  <  command-line flags
(lowest precedence)                                    (highest precedence)
#include <stdlib.h>
#include <string.h>

const char *resolve_output_dir(const char *cli_flag) {
    if (cli_flag) return cli_flag;                 // 1. explicit flag wins
    const char *env = getenv("APP_OUTPUT_DIR");
    if (env) return env;                            // 2. then environment
    return "./output";                              // 3. then a hardcoded default
    // a real tool would also check a config file here, between env and default
}

Real-world example. This precedence idea is why git -c user.name=X commit overrides ~/.gitconfig, which overrides git's built-in defaults. Many serious CLIs (curl, docker, kubectl) use some version of this layered-override convention, because it lets a script override one setting for one invocation without touching global config.

24.3 Exit codes are part of your program's API

// Conventionally (see <sysexits.h> on BSD/macOS for the fuller list):
// 0   = success
// 1   = general runtime error
// 2   = usage error (bad arguments)
// 126 = command found but not executable
// 127 = command not found
// 128+N = terminated by signal N

Shell scripts and CI pipelines branch on your exit code — returning 0 after a caught, logged error (instead of 1) is a real and common bug class that silently turns failures into "success" in automation.


Generated from real-life-c-guide.md · 8 Oct 2026 16:36