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;
}