Companion code · Chapter 7

Structs, Headers, and Multi-File Programs

8 files. About this chapter.

Download ch07.zip

ch07/Makefile22 lines
# Makefile for stn_report. Recipe lines must start with a TAB.
CC      = gcc
CFLAGS  = -std=c17 -Wall -Wextra -Werror -g \
          -fsanitize=address,undefined
LDFLAGS = -fsanitize=address,undefined
OBJS    = main.o reading.o sensor_db.o

stn_report: $(OBJS)
	$(CC) $(LDFLAGS) -o stn_report $(OBJS)

main.o: main.c reading.h sensor_db.h station.h
	$(CC) $(CFLAGS) -c main.c

reading.o: reading.c reading.h station.h
	$(CC) $(CFLAGS) -c reading.c

sensor_db.o: sensor_db.c sensor_db.h station.h
	$(CC) $(CFLAGS) -c sensor_db.c

.PHONY: clean
clean:
	rm -f $(OBJS) stn_report
ch07/main.c34 lines
/* main.c: stn_report, a tiny report over a few readings. */
#include <stdio.h>
#include "reading.h"
#include "sensor_db.h"

int main(void) {
    struct stn_db *db = stn_db_create("north-field", 8);   /* we own db */
    if (db == NULL) {
        fprintf(stderr, "stn_report: out of memory\n");
        return 1;
    }
    const struct reading sample[] = {
        { 1001, 60, 21.75, STN_FLAG_VALID | STN_FLAG_CALIBRATED },
        { 1002, 60, 64.0, STN_FLAG_VALID },
        { 1001, 120, 21.25, STN_FLAG_VALID },
        { 1001, 180, 99.0, STN_FLAG_OUT_OF_RANGE },          /* not VALID */
    };
    for (size_t i = 0; i < sizeof sample / sizeof sample[0]; i++) {
        if (stn_db_add(db, &sample[i]) != 0) {
            fprintf(stderr, "stn_report: db full\n");
        }
    }
    char line[64];                                    /* we own line */
    printf("%s: %zu readings\n", stn_db_name(db), stn_db_count(db));
    for (size_t i = 0; i < stn_db_count(db); i++) {
        stn_reading_format(stn_db_get(db, i), line, sizeof line);
        printf("  %s\n", line);
    }
    size_t n = 0;
    double mean = stn_db_mean(db, 1001, &n);
    printf("sensor 1001: mean %.2f over %zu valid readings\n", mean, n);
    stn_db_destroy(db);                     /* db's pointers die here */
    return 0;
}
ch07/reading.c25 lines
/* reading.c: definitions for the functions declared in reading.h. */
#include <stdio.h>
#include "reading.h"

static const char *const UNITS[] = { "C", "%RH", "hPa", "m/s" };

int stn_reading_kind(const struct reading *r) {
    uint32_t local = r->sensor_id % 1000;   /* 1001 -> 1, 2003 -> 3 */
    if (local < 1 || local > 4) {
        return -1;
    }
    return (int)(local - 1);                /* SENSOR_TEMP is 0 */
}

const char *stn_reading_unit(const struct reading *r) {
    int kind = stn_reading_kind(r);
    return kind < 0 ? "?" : UNITS[kind];
}

int stn_reading_format(const struct reading *r, char *buf, size_t cap) {
    return snprintf(buf, cap, "%u t=%lld %.2f %s%s",
                    (unsigned)r->sensor_id, (long long)r->time_s,
                    r->value, stn_reading_unit(r),
                    (r->flags & STN_FLAG_VALID) ? "" : " (invalid)");
}
ch07/reading.h25 lines
/* reading.h: operations on one struct reading. */
#ifndef READING_H
#define READING_H

#include <stddef.h>   /* size_t */
#include "station.h"

/* Sensor kind for r's id (xx01 temp ... xx04 wind),
 * or -1 if unknown. */
int stn_reading_kind(const struct reading *r);

/* Unit name for r's kind, such as "C" or "hPa".
 * OWNERSHIP: returns a pointer to a string literal.
 * Do not free it. */
const char *stn_reading_unit(const struct reading *r);

/* Writes a one-line description of r into buf.
 * OWNERSHIP: buf is borrowed; the caller owns it and its
 * cap bytes. The result is always terminated. Returns the
 * length snprintf wanted; a value >= cap means the text
 * was cut short. */
int stn_reading_format(const struct reading *r, char *buf,
                       size_t cap);

#endif /* READING_H */
ch07/sensor_db.c76 lines
/* sensor_db.c: the only file that knows what is inside struct stn_db. */
#include <stdlib.h>
#include <string.h>
#include "sensor_db.h"
struct stn_db {
    char *name;            /* owned: heap copy of the name */
    struct reading *rows;  /* owned: capacity slots */
    size_t count;          /* slots in use */
    size_t capacity;
};

struct stn_db *stn_db_create(const char *name, size_t capacity) {
    if (capacity == 0) {
        return NULL;
    }
    struct stn_db *db = malloc(sizeof *db);
    if (db == NULL) {
        return NULL;
    }
    db->name = malloc(strlen(name) + 1);
    db->rows = calloc(capacity, sizeof *db->rows);
    if (db->name == NULL || db->rows == NULL) {
        free(db->name);      /* free(NULL) is allowed and does nothing */
        free(db->rows);
        free(db);
        return NULL;
    }
    strcpy(db->name, name);
    db->count = 0;
    db->capacity = capacity;
    return db;
}

void stn_db_destroy(struct stn_db *db) {
    if (db != NULL) {
        free(db->rows);
        free(db->name);
        free(db);
    }
}

int stn_db_add(struct stn_db *db, const struct reading *r) {
    if (db->count == db->capacity) {
        return -1;
    }
    db->rows[db->count] = *r;          /* struct assignment copies */
    db->count++;
    return 0;
}

size_t stn_db_count(const struct stn_db *db) {
    return db->count;
}

const struct reading *stn_db_get(const struct stn_db *db, size_t i) {
    return i < db->count ? &db->rows[i] : NULL;
}

const char *stn_db_name(const struct stn_db *db) {
    return db->name;
}

double stn_db_mean(const struct stn_db *db, uint32_t sensor_id,
                   size_t *n_out) {
    double sum = 0.0;
    size_t n = 0;
    for (size_t i = 0; i < db->count; i++) {
        const struct reading *r = &db->rows[i];
        if (r->sensor_id == sensor_id && (r->flags & STN_FLAG_VALID)) {
            sum += r->value;
            n++;
        }
    }
    *n_out = n;
    return n > 0 ? sum / (double)n : 0.0;
}
ch07/sensor_db.h39 lines
/* sensor_db.h: a fixed-capacity in-memory table of readings */
#ifndef SENSOR_DB_H
#define SENSOR_DB_H

#include <stddef.h>
#include "station.h"

struct stn_db;   /* declared here; defined in sensor_db.c */

/* OWNERSHIP RULES (the type system cannot say these; we must)
 * 1. stn_db_create returns a new db, or NULL if out of
 *    memory or capacity is 0. The caller owns it and calls
 *    stn_db_destroy exactly once.
 * 2. The db stores its own copy of name. The caller still
 *    owns the string it passed and may change or free it
 *    afterward.
 * 3. stn_db_add copies *r into the db and keeps no pointer
 *    to r.
 * 4. stn_db_get and stn_db_name return BORROWED pointers
 *    into the db. Never free them. They dangle after
 *    stn_db_destroy.
 */
struct stn_db *stn_db_create(const char *name, size_t capacity);
void stn_db_destroy(struct stn_db *db);      /* NULL: no-op */

int stn_db_add(struct stn_db *db, const struct reading *r);
                                  /* 0 on success, -1 if full */
size_t stn_db_count(const struct stn_db *db);
const struct reading *stn_db_get(const struct stn_db *db,
                                 size_t i);
                                  /* NULL if i >= count */
const char *stn_db_name(const struct stn_db *db);

/* Mean of VALID readings from sensor_id; stores how many
 * in *n_out. Returns 0.0 when *n_out is 0. */
double stn_db_mean(const struct stn_db *db, uint32_t sensor_id,
                   size_t *n_out);

#endif /* SENSOR_DB_H */
ch07/station.h29 lines
/* station.h: types shared by every station module.
 * Types and constants only: no variables, no functions. */
#ifndef STATION_H
#define STATION_H

#include <stdint.h>

enum sensor_kind { SENSOR_TEMP, SENSOR_HUMIDITY,
                   SENSOR_PRESSURE, SENSOR_WIND };

#define STN_FLAG_VALID        0x01
#define STN_FLAG_CALIBRATED   0x02
#define STN_FLAG_LOW_BATTERY  0x04
#define STN_FLAG_OUT_OF_RANGE 0x08

/* One sample from one sensor.
 * OWNERSHIP: a plain value with no pointers inside. Copy it
 * with =; there is nothing to free.
 * LAYOUT: chosen by the compiler and the target, padding
 * included. Never fwrite the struct itself; write the
 * fields one at a time. */
struct reading {
    uint32_t sensor_id;   /* e.g. 1001 */
    int64_t  time_s;      /* seconds since station start */
    double   value;       /* in the sensor's unit */
    uint8_t  flags;       /* STN_FLAG_* bits */
};

#endif /* STATION_H */
ch07/stn_single.c137 lines
/* stn_single.c: the station report in one file.
 * Chapter 7 lab: split this into station.h, reading.h/.c,
 * sensor_db.h/.c and main.c. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STN_FLAG_VALID        0x01
#define STN_FLAG_CALIBRATED   0x02
#define STN_FLAG_LOW_BATTERY  0x04
#define STN_FLAG_OUT_OF_RANGE 0x08

enum sensor_kind { SENSOR_TEMP, SENSOR_HUMIDITY,
                   SENSOR_PRESSURE, SENSOR_WIND };

struct reading {
    uint32_t sensor_id;
    int64_t  time_s;
    double   value;
    uint8_t  flags;
};

struct stn_db {
    char *name;
    struct reading *rows;
    size_t count;
    size_t capacity;
};

const char *const UNITS[] = { "C", "%RH", "hPa", "m/s" };

int stn_reading_kind(const struct reading *r) {
    uint32_t local = r->sensor_id % 1000;
    if (local < 1 || local > 4) {
        return -1;
    }
    return (int)(local - 1);
}

const char *stn_reading_unit(const struct reading *r) {
    int kind = stn_reading_kind(r);
    return kind < 0 ? "?" : UNITS[kind];
}

int stn_reading_format(const struct reading *r, char *buf,
                       size_t cap) {
    return snprintf(buf, cap, "%u t=%lld %.2f %s%s",
                    (unsigned)r->sensor_id, (long long)r->time_s,
                    r->value, stn_reading_unit(r),
                    (r->flags & STN_FLAG_VALID) ? "" : " (invalid)");
}

struct stn_db *stn_db_create(const char *name, size_t capacity) {
    if (capacity == 0) {
        return NULL;
    }
    struct stn_db *db = malloc(sizeof *db);
    if (db == NULL) {
        return NULL;
    }
    db->name = malloc(strlen(name) + 1);
    db->rows = calloc(capacity, sizeof *db->rows);
    if (db->name == NULL || db->rows == NULL) {
        free(db->name);
        free(db->rows);
        free(db);
        return NULL;
    }
    strcpy(db->name, name);
    db->count = 0;
    db->capacity = capacity;
    return db;
}

void stn_db_destroy(struct stn_db *db) {
    if (db != NULL) {
        free(db->rows);
        free(db->name);
        free(db);
    }
}

int stn_db_add(struct stn_db *db, const struct reading *r) {
    if (db->count == db->capacity) {
        return -1;
    }
    db->rows[db->count] = *r;
    db->count++;
    return 0;
}

double stn_db_mean(const struct stn_db *db, uint32_t sensor_id,
                   size_t *n_out) {
    double sum = 0.0;
    size_t n = 0;
    for (size_t i = 0; i < db->count; i++) {
        const struct reading *r = &db->rows[i];
        if (r->sensor_id == sensor_id && (r->flags & STN_FLAG_VALID)) {
            sum += r->value;
            n++;
        }
    }
    *n_out = n;
    return n > 0 ? sum / (double)n : 0.0;
}

int main(void) {
    struct stn_db *db = stn_db_create("north-field", 8);
    if (db == NULL) {
        fprintf(stderr, "stn_report: out of memory\n");
        return 1;
    }
    const struct reading sample[] = {
        { 1001, 60, 21.75, STN_FLAG_VALID | STN_FLAG_CALIBRATED },
        { 1002, 60, 64.0, STN_FLAG_VALID },
        { 1001, 120, 21.25, STN_FLAG_VALID },
        { 1001, 180, 99.0, STN_FLAG_OUT_OF_RANGE },
    };
    for (size_t i = 0; i < sizeof sample / sizeof sample[0]; i++) {
        if (stn_db_add(db, &sample[i]) != 0) {
            fprintf(stderr, "stn_report: db full\n");
        }
    }
    char line[64];
    /* main reaches into the struct directly: the lab removes this */
    printf("%s: %zu readings\n", db->name, db->count);
    for (size_t i = 0; i < db->count; i++) {
        stn_reading_format(&db->rows[i], line, sizeof line);
        printf("  %s\n", line);
    }
    size_t n = 0;
    double mean = stn_db_mean(db, 1001, &n);
    printf("sensor 1001: mean %.2f over %zu valid readings\n", mean, n);
    stn_db_destroy(db);
    return 0;
}