学习笔记 · Obsidian
C 第一课:把一组采样值变成设备状态
先建立一个和后端开发不同的习惯
在 C 中,除了问“值是什么”,还要问“它用什么类型保存、占用的存储在哪里、这次调用可以访问多长、谁负责其生命周期”。今天先把这些问题放进一个可运行的小实验。
下例的数值是人工构造的采样单位,不来自 CoreS3 传感器。没有硬件也能练习;将来只替换采样来源,保留统计逻辑。
读懂这些类型
| 写法 | 此处的意思 | 选择理由 |
|---|---|---|
uint16_t | 16 位无符号整数 | 每个输入限定为非负整数 |
uint32_t | 32 位无符号整数 | 累加使用更宽的范围 |
size_t | sizeof 结果所使用的无符号类型 | 表示元素数量和索引 |
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;
}
沿数据流理解
samples是连续的输入数组;调用时提供首元素位置和有效元素数。GNU Arraysconst uint16_t *samples表示通过这个指针读取数据,不通过它修改数据。sample_summary_t *out则接收结果对象的位置。GNU Pointersif把无效输入挡在循环外;for只访问0到count - 1。- 本实验至多累加 8 个
uint16_t,最大和为8 × 65535 = 524280,在uint32_t范围内。允许更多输入时,要重新证明累加不会溢出。 sum / count使用整数除法;本例得到1500。输出窄化之前,平均值仍不超过输入最大值。&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 实机通过。