---
type: study
status: active
created: 2026-09-10
updated: 2026-09-10
sensitivity: private
tags: [study, embedded, c, esp32, cores3]
sources:
  - "https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf"
  - "https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Arrays.html"
  - "https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Pointers.html"
---
# C 第一课：把一组采样值变成设备状态

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

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

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

## 读懂这些类型

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

精确宽度类型由 `<stdint.h>` 提供，前提是实现支持对应宽度；本例运行目标支持。不要把 `int`、`long` 在 Mac 上的大小直接带到 MCU 上。类型声明不能代替输入范围检查。[类型规范：N1570 §7.20](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)

## 一个完整练习

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

```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](https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Arrays.html)
2. `const uint16_t *samples` 表示通过这个指针读取数据，不通过它修改数据。`sample_summary_t *out` 则接收结果对象的位置。[GNU Pointers](https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Pointers.html)
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 时，在保存源文件的目录执行：

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

预期：

```text
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`。

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

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

---

[[Study/embedded-cores3/CoreS3学习入口|返回学习入口]] · [[Study/embedded-cores3/CoreS3资料来源与版本|来源与版本]]
