Frontend Labs
⋯

9. Mini projects: a unit converter and a word-frequency counter

9.1 Mini project — CLI unit converter

Goal: apply everything above (parsing argv, strtod, switch, snprintf) in one small, real tool.

// unitconv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    const char *from, *to;
    double factor;   // multiply "from" units by factor to get "to" units
} Conversion;

static const Conversion TABLE[] = {
    {"km", "mi", 0.621371},
    {"mi", "km", 1.60934},
    {"kg", "lb", 2.20462},
    {"lb", "kg", 0.453592},
    {"c",  "f",  0},  // handled specially below
    {"f",  "c",  0},
};
#define TABLE_LEN (sizeof(TABLE) / sizeof(TABLE[0]))

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "usage: %s <value> <from> <to>\n", argv[0]);
        return 1;
    }

    char *end;
    double value = strtod(argv[1], &end);
    if (*end != '\0') {
        fprintf(stderr, "error: '%s' is not a number\n", argv[1]);
        return 1;
    }
    const char *from = argv[2], *to = argv[3];

    if (strcmp(from, "c") == 0 && strcmp(to, "f") == 0) {
        printf("%.2f\n", value * 9.0 / 5.0 + 32.0);
        return 0;
    }
    if (strcmp(from, "f") == 0 && strcmp(to, "c") == 0) {
        printf("%.2f\n", (value - 32.0) * 5.0 / 9.0);
        return 0;
    }

    for (size_t i = 0; i < TABLE_LEN; i++) {
        if (strcmp(TABLE[i].from, from) == 0 && strcmp(TABLE[i].to, to) == 0) {
            printf("%.4f\n", value * TABLE[i].factor);
            return 0;
        }
    }

    fprintf(stderr, "error: no conversion from '%s' to '%s'\n", from, to);
    return 1;
}
clang -std=c17 -Wall -Wextra -Werror -o unitconv unitconv.c
./unitconv 10 km mi     # 6.2137
./unitconv 100 c f      # 212.00

9.2 Mini project — word-frequency counter

Goal: fgets, strtok, a fixed-size hash-free lookup via linear scan (you'll replace this with a real hash table in Section 18), and dynamic memory ownership done correctly for the first time.

// wordfreq.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

typedef struct {
    char *word;
    int   count;
} Entry;

#define MAX_WORDS 10000

static Entry entries[MAX_WORDS];
static int   num_entries = 0;

static int record(const char *word) {
    for (int i = 0; i < num_entries; i++) {
        if (strcmp(entries[i].word, word) == 0) {
            entries[i].count++;
            return 1;
        }
    }
    if (num_entries >= MAX_WORDS) return 1;
    entries[num_entries].word = strdup(word);   // heap copy — word buffer gets reused/mutated
    if (!entries[num_entries].word) return 0;
    entries[num_entries].count = 1;
    num_entries++;
    return 1;
}

static int by_count_desc(const void *a, const void *b) {
    const Entry *ea = a, *eb = b;
    return (eb->count > ea->count) - (eb->count < ea->count);  // negative/zero/positive
}

int main(void) {
    char line[1024];
    while (fgets(line, sizeof line, stdin)) {
        for (char *p = line; *p; p++) *p = (char)tolower((unsigned char)*p);
        char *tok = strtok(line, " \t\n.,!?;:\"'()");
        while (tok) {
            if (!record(tok)) { perror("strdup"); return 1; }
            tok = strtok(NULL, " \t\n.,!?;:\"'()");
        }
    }

    qsort(entries, (size_t)num_entries, sizeof(Entry), by_count_desc);

    for (int i = 0; i < num_entries && i < 20; i++) {
        printf("%5d  %s\n", entries[i].count, entries[i].word);
    }

    for (int i = 0; i < num_entries; i++) free(entries[i].word);  // own what you strdup
    return 0;
}
clang -std=gnu17 -Wall -Wextra -Werror -o wordfreq wordfreq.c   # gnu17: wordfreq.c calls
                                                                  # strdup(), a POSIX function
                                                                  # strict c17 hides on Linux/glibc
curl -s https://www.gutenberg.org/files/1342/1342-0.txt | ./wordfreq | head

Note the ownership rule established here and used for the rest of the guide: whoever calls strdup/malloc is responsible for the matching free. record() allocates; main()'s final loop frees. This convention — and what happens when you break it — is the entire subject of Part II.


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