数据归位:struct 成员与 static 变量
05 数据归位:struct 成员与 static 变量
C 语言 OOP 系列导航
本系列面向会 C 和单片机基础、刚进入企业项目的同学,从
struct封装一路讲到ops、多态和类 Linux 驱动框架。
很多新人听到“不要用全局变量”,会以为这是代码风格问题。其实更准确地说:不要让数据失去主人。
下面这种写法把 LED 模块的所有数据都放在全局:
1 | int g_pin = 0; |
初始化函数依赖这些全局变量:
1 | int bad_led_init(uint8_t pin) |
调用两次后,问题就出现了:
1 | bad_led_init(5); /* 初始化第一颗 LED */ |
一句话先懂:对象自己的数据进 struct,模块共享的数据加 static,不该被修改的数据加 const。
先看问题:变量有值,不代表值属于当前对象
全局变量最容易制造这种错觉:代码能编译,函数能调用,变量也有值,但它已经被别的对象覆盖了。
| 表面现象 | 实际问题 |
|---|---|
| 编译正常 | 数据归属错了 |
| 函数能调用 | 操作对象错了 |
| 变量有值 | 值已经被别的对象覆盖 |
| 日志看似合理 | 硬件行为不对 |
所以判断一个变量该放哪里,不要先问“哪里写起来方便”,而要问“它属于谁”。
数据按归属分类
| 数据类型 | 应该放哪里 | 示例 |
|---|---|---|
| 每个对象各有一份 | struct 成员 |
pin、brightness、is_on |
| 整个模块共享一份 | .c 文件里的 static 变量 |
s_init_count、s_debug_flag |
| 不应该被修改 | static const |
MAX_BRIGHTNESS、MAX_PIN |
这张表是写 C 模块时很实用的判断标准。变量放对位置,后面很多 bug 自然消失。
实例数据进入 struct
每颗 LED 自己的数据放进对象本身:
1 | typedef struct { |
初始化时写入对象自己的字段:
1 | int led_init(Led_t *me, uint8_t pin) |
红灯和绿灯互不覆盖:
1 | Led_t red; |
模块共享数据用 static
有些数据确实不属于某一颗 LED,而属于整个 LED 模块。例如初始化次数、调试开关:
1 | static int s_init_count = 0; |
static 修饰文件作用域变量时,表示这份变量只在当前 .c 文件可见。外部无法用 extern 直接拿到它:
1 | extern int s_init_count; /* 链接失败:static 变量没有外部链接 */ |
如果外部只需要读取,就提供查询接口:
1 | int led_get_init_count(void) |
这样模块仍然是这份数据唯一的写入方。
常量用 static const
下面这种写法只是名字像常量,实际上仍然能被改:
1 | int MAX_BRIGHTNESS = 255; |
更稳的写法:
1 | static const uint8_t MAX_BRIGHTNESS = 100; |
| 写法 | 作用 |
|---|---|
static |
只在当前文件可见 |
const |
不允许修改 |
uint8_t |
类型明确 |
| 保留变量名 | 调试时比宏更友好 |
完整片段
三类数据放好后,代码会变得更容易推理:
1 | static const uint8_t MAX_BRIGHTNESS = 100; |
led_set_brightness(&red, 80) 只应该影响 red,不应该偷偷影响 green。这就是数据归位的价值。
static 的三种常见用法
| 用法 | 含义 | 建议 |
|---|---|---|
| 修饰函数 | 当前文件私有函数 | 推荐,用来隐藏 helper |
| 修饰全局变量 | 当前文件私有变量 | 推荐,用来保存模块状态 |
| 修饰局部变量 | 函数结束后值仍保留 | 谨慎,容易制造隐藏状态 |
前两种是模块化设计常用工具;第三种会让函数内部带着跨调用状态,测试和复现都更麻烦。
新人常见误区
| 误区 | 更稳的做法 |
|---|---|
所有模块状态都用 g_ 全局变量 |
先判断属于对象还是模块 |
static 变量更安全,所以随便用 |
它只是限制可见性,不解决归属判断 |
| 常量用大写变量名就行 | 用 static const 明确不可修改 |
| 为了省参数,把当前对象存全局 | 多实例时会立刻互相覆盖 |
C/C++ 对照
| C 写法 | C++ 写法 |
|---|---|
me->pin |
this->pin |
static int s_init_count |
static int Led::init_count |
static const uint8_t MAX |
static const / constexpr |
static bool helper() |
private 方法 |
led_get_init_count() |
静态 getter |
嵌入式项目意义
嵌入式系统里的“全局变量满天飞”会带来实际问题:
| 问题 | 影响 |
|---|---|
| 多实例互相覆盖 | 多路外设无法独立工作 |
外部随便 extern |
模块边界失效 |
| 运行时改常量 | 参数约束被破坏 |
| 隐藏状态太多 | 测试复现困难 |
总结
对象自己的数据进
struct,模块共享的数据加static,不该改的数据加const;数据没有主人,bug 就是主人。










