学习笔记 · Obsidian

C 第一课:把一组采样值变成设备状态

ESP-IDFC / C++

先建立一个和后端开发不同的习惯

在 C 中,除了问“值是什么”,还要问“它用什么类型保存、占用的存储在哪里、这次调用可以访问多长、谁负责其生命周期”。今天先把这些问题放进一个可运行的小实验。

下例的数值是人工构造的采样单位,不来自 CoreS3 传感器。没有硬件也能练习;将来只替换采样来源,保留统计逻辑。

读懂这些类型

写法此处的意思选择理由
uint16_t16 位无符号整数每个输入限定为非负整数
uint32_t32 位无符号整数累加使用更宽的范围
size_tsizeof 结果所使用的无符号类型表示元素数量和索引
bool真或假独立表达成功与失败
struct多个字段组成的数据结构一次返回平均值和报警状态

精确宽度类型由 <stdint.h> 提供,前提是实现支持对应宽度;本例运行目标支持。不要把 int、long 在 Mac 上的大小直接带到 MCU 上。类型声明不能代替输入范围检查。类型规范:N1570 §7.20

一个完整练习

保存为代码工作目录里的 lesson01.c。下面这个代码块是本课唯一完整可执行程序;后续课里的片段用于解释局部行为。

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#define MAX_SAMPLES 8u
#define ALARM_THRESHOLD 1500u

typedef struct {
    uint16_t average;
    bool alarm;
} sample_summary_t;

static bool summarize(const uint16_t *samples, size_t count,
                      sample_summary_t *out)
{
    if (samples == NULL || out == NULL ||
        count == 0u || count > MAX_SAMPLES) {
        return false;
    }

    uint32_t sum = 0u;
    for (size_t i = 0u; i < count; ++i) {
        sum += samples[i];
    }

    out->average = (uint16_t)(sum / (uint32_t)count);
    out->alarm = out->average >= ALARM_THRESHOLD;
    return true;
}

int main(void)
{
    const uint16_t samples[] = {1000u, 1200u, 1800u, 2000u};
    const size_t count = sizeof samples / sizeof samples[0];
    sample_summary_t summary = {0};

    if (!summarize(samples, count, &summary)) {
        fputs("invalid input\n", stderr);
        return 1;
    }

    printf("count=%zu average=%u alarm=%s\n", count,
           (unsigned)summary.average, summary.alarm ? "yes" : "no");
    return 0;
}

沿数据流理解

  1. samples 是连续的输入数组;调用时提供首元素位置和有效元素数。GNU Arrays
  2. const uint16_t *samples 表示通过这个指针读取数据,不通过它修改数据。sample_summary_t *out 则接收结果对象的位置。GNU Pointers
  3. if 把无效输入挡在循环外;for 只访问 0 到 count - 1。
  4. 本实验至多累加 8 个 uint16_t,最大和为 8 × 65535 = 524280,在 uint32_t 范围内。允许更多输入时,要重新证明累加不会溢出。
  5. sum / count 使用整数除法;本例得到 1500。输出窄化之前,平均值仍不超过输入最大值。
  6. &summary 取得结果对象地址,out->average 修改该对象中的字段。函数返回值另行表示是否成功;失败时输出对象不变。

这里的 API 契约还要求:samples 确实指向至少 count 个有效元素,out 指向可写对象,二者存储不重叠。函数不能仅凭一个地址验证调用方是否提供了足够内存。

编译与运行

Mac 已安装 Xcode Command Line Tools / Clang 时,在保存源文件的目录执行:

clang -std=c17 -Wall -Wextra -Wconversion -Werror \
  -fsanitize=address,undefined -g lesson01.c -o lesson01
./lesson01

预期:

count=4 average=1500 alarm=yes

-std=c17 是这个本机练习的教学口径,不代表 ESP-IDF 的默认 C 标准。编译警告帮助发现类型问题,Sanitizer 帮助发现部分运行时错误;两者都不能证明所有输入安全。

动手改三次

  • 把数组改成 {100u, 200u, 300u},预期平均值 200、报警 no。
  • 把输入改成 8 个 65535u,预期平均值 65535,不因累加发生回绕。
  • 调用时令 count = 0,预期报 invalid input 并返回退出码 1。

- 先解释,再展开参考答案 为什么不是返回一个平均数就够了?因为 0 本身可以是有效结果,不能同时代表失败。为什么要 &summary?因为函数需要改动调用方持有的对象,而不是一份结构体副本。

本课验证状态见 Progress;本机练习通过不代表用户已经掌握,也不代表 CoreS3 实机通过。


返回学习入口 · 来源与版本