---
type: study
status: active
created: 2026-09-10
updated: 2026-09-10
sensitivity: private
tags: [study, embedded, c, esp32, cores3]
sources:
  - "https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Bitwise-Operations.html"
  - "https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Structures.html"
  - "https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html"
---
# C 位运算、结构体和模块边界

## 用位描述多个开关

一个数可以同时保存多个独立状态，例如 bit 0 表示联网、bit 1 表示数据有效、bit 2 表示报警。位编号从 0 开始。

```c
uint32_t flags = 0u;
const uint32_t online = UINT32_C(1) << 0;
const uint32_t alarm = UINT32_C(1) << 2;
flags |= online;                    /* 置位 */
flags |= alarm;
flags &= ~alarm;                    /* 清位 */
bool connected = (flags & online) != 0u;
flags ^= online;                    /* 翻转 */
```

`&` 是逐位与，`&&` 是逻辑判断并会短路；`|` 和 `||` 也不同。写位运算条件时保留括号。这个片段假设已包含 `<stdint.h>`、`<stdbool.h>`。[GNU Bitwise Operations](https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Bitwise-Operations.html)

本例使用无符号值。动态移位前，先验证位数；这里若左操作数按 32 位计算，移位量须在 `0..31`。不要以为 `1 << 31` 和明确的无符号掩码在所有实现上等价。

## 位掩码不是硬件寄存器通用写法

上例改的是普通内存变量。真实寄存器可能有“写 1 清除”、只读位或保留位；未经数据手册确认就执行“读、改、写”，可能误清中断。初学先用 ESP-IDF 驱动 API，再读驱动如何访问寄存器。

这也是软件抽象的作用：调用者表达动作，驱动负责寄存器语义和访问顺序。

## struct 与 enum 各负责什么

```c
typedef enum {
    DEVICE_STARTING,
    DEVICE_OFFLINE,
    DEVICE_ONLINE,
    DEVICE_FAULT
} device_state_t;

typedef struct {
    uint32_t sequence;
    uint16_t reading;
    bool valid;
    device_state_t state;
} device_snapshot_t;
```

`enum` 给状态命名，`struct` 组合某一时刻的快照。[GNU Structures](https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Structures.html)

本课程设计采用 `valid` 独立表达有效性。采样失败时，不能把 `reading = 0` 当作错误，因为零也可能是合法读数。状态迁移则由事件决定，例如“获得 IP”不等同于“MQTT 已连接”。

结构体适合进程内数据组织。对外协议应逐字段编码，定义字节序、长度和版本；不要把 `sizeof(struct)` 个字节直接当网络协议。这样可以绕开填充、枚举表示和编译器差异。

## static、宏和头文件

- 文件作用域的 `static` 函数或变量，把链接可见范围限制在当前翻译单元；函数内的 `static` 对象则跨调用保留值。
- `#include` 在预处理阶段展开头文件；头文件保存声明和必要类型，实现留在 `.c`。
- 头文件用 include guard 防重复展开；不要把有外部链接的全局变量定义随意放进头文件。
- `#define` 进行文本替换。像 `SQUARE(i++)` 一类会重复求值的宏，应改成普通函数或 `static inline` 函数。

声明、存储类别和预处理的规范入口分别为 [N1570 §6.7、§6.10](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)。这里只使用与本课程模块划分相关的最小语义。

## volatile 的实际边界

`volatile` 约束编译器对特定访问的优化。它不会自动给 `count++` 加锁，也不是发布普通内存写入的同步屏障。共享状态优先用队列、互斥量或经验证的原子操作。[GCC Volatiles](https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html)

假设任务 A 和 B 同时执行 `count++`：都读到 5，再都写回 6，就丢失一次更新。给 `count` 加 `volatile` 仍无法把读取、加法和写回变成一个不可分割操作。

## 练习

从 `flags = 0` 开始，依次执行置 bit 0、置 bit 2、清 bit 0。预期结果依次为十进制 `1、5、4`。解释为什么 `(flags & alarm) != 0` 可读，而 `flags && alarm` 不能判断特定 bit。

将第 01 课拆成 `sample.h`、`sample.c`、`main.c`：类型和公开声明进头文件，函数实现进 `.c`。跨文件调用的 `summarize` 此时应去掉文件作用域 `static`；不要原样复制后再疑惑链接失败。

---

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