05 数据归位:你的全局变量,该死了
05 数据归位:你的全局变量,该死了
C 语言 OOP 系列导航
全局变量的问题,不是”看起来不优雅”。真正的问题是:它让数据没有主人。数据没有主人,谁都能改;谁都能改,就很难知道 bug 是谁带来的。
看一眼反面写法
1 | int g_pin = 0; |
初始化函数:
1 | int bad_led_init(uint8_t pin) |
然后你这样用:
1 | bad_led_init(5); /* 红灯 */ |
第二次初始化把 g_pin 覆盖了。这类 bug 很隐蔽 — 编译正常,函数能调用,变量有值,日志看似合理,但硬件行为偏偏不对。
数据归位:每一份数据都要有主人
先把数据分成三类:
| 数据类型 | 应该放哪里 | 示例 |
|---|---|---|
| 每个对象各有一份 | struct 成员 |
pin、brightness、is_on |
| 整个模块共享一份 | .c 文件里的 static 变量 |
s_init_count、s_debug_flag |
| 不应该被修改 | static const |
MAX_BRIGHTNESS、MAX_PIN |
判断标准很简单:如果这个数据跟着”某一个对象”走,就放进对象;如果它属于”整个模块”,就放进 .c 并加 static;如果它不该变,就加 const。
实例数据进 struct
正确写法:
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 文件可见。外部不能这样乱改:
1 | extern int s_init_count; /* 不可行,static 变量没有外部链接 */ |
如果外部想读取,可以提供接口:
1 | int led_get_init_count(void) |
数据的主人说了算。
常量用 static const
反面写法:
1 | int MAX_BRIGHTNESS = 255; |
这是变量,不是常量。别人可以 MAX_BRIGHTNESS = 999;。
正确写法:
1 | static const uint8_t MAX_BRIGHTNESS = 100; |
| 写法 | 作用 |
|---|---|
static |
只在当前文件可见 |
const |
不允许修改 |
uint8_t |
类型明确 |
| 变量名保留 | 调试时更友好 |
完整片段
1 | static const uint8_t MAX_BRIGHTNESS = 100; |
static 的三种含义
static 在 C 语言里有三种常见用法:
| 用法 | 含义 | 本系列建议 |
|---|---|---|
| 修饰函数 | 当前文件私有函数 | 推荐,用来隐藏 helper |
| 修饰全局变量 | 当前文件私有变量 | 推荐,用来保存模块状态 |
| 修饰局部变量 | 函数结束后值仍保留 | 谨慎,容易制造隐藏状态 |
前两种是模块化设计的常用工具。第三种不是不能用,但会让函数内部有记忆,测试和调试都会更复杂。
嵌入式项目意义
嵌入式系统里的”全局变量满天飞”会带来很多实际问题:多实例互相覆盖导致多路外设无法独立工作,外部随便 extern 让模块边界失效,运行时改常量破坏参数约束,隐藏状态太多导致测试复现困难。
数据归位后,代码会更容易推理 — led_on(&red) 这行代码只应该影响 red,不应该偷偷影响 green。
我见过的一个坑:同事休假期间,有人往 g_pin 写了新值,返工后 debug 了三天才发现是多实例覆盖。数据没有主人,bug 就是主人。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Rvosy的小破站!










