第一部分:static 一般在哪里用?
static 在 C 语言里是个"多面手",它的核心作用是限定作用域 和延长生命周期。在嵌入式开发中,通常用在以下三个地方:
1. 修饰局部变量(延长生命周期)
-
用法:写在函数内部的变量前。
-
效果 :这个变量不再存放在栈(Stack)里,而是存放在静态存储区(全局区)。函数退出后,它的值不会丢失,下次调用该函数时,它还是上次的值。它只会被初始化一次。
-
场景:状态机记录当前状态、计数器、或者防止重复初始化的标志位。
-
你的代码里就有 :你之前发的
static uint16_t u16CurrentRefVol_X100 = EXTERNAL_REF_VOLT_X100;。它放在函数外面(其实属于下面第 2 点),但保留了上次运行的值。
2. 修饰全局变量(限定作用域)
-
用法:写在函数外部的变量前。
-
效果 :这个变量只能被当前
.c文件内的函数访问 。其他文件就算用extern声明也找不到它。 -
场景 :模块内部的私有变量。比如蓝牙模块里的"接收缓冲区",你不想让主函数随便乱动,就加上
static。这体现了封装思想 ,防止命名冲突(两个文件里都可以有叫static int flag;的变量,互不干扰)。
3. 修饰函数(限定作用域)
-
用法:写在函数定义前。
-
效果 :这个函数只能被当前
.c文件内的代码调用。 -
场景 :模块的内部辅助函数。比如你在
app_battery.c里写了一个计算平均值的函数,只有这个文件里的App_Battery_Task()会用它,那就加上static。这样能防止别的文件瞎调用,也减少了全局符号表的长度,节省编译器链接时间。
总结 :在工程架构中,除了你要对外暴露的接口(放在 .h 里),其他所有的全局变量和内部函数,统统加上 static。 这是高手的标配。
第二部分:ROM 和 RAM 一般怎么占用的?
单片机内部通常有 Flash(ROM,掉电不丢失) 和 SRAM(RAM,掉电丢失)。
当你写下一行代码,编译器会把它大卸八块,分别塞进 Flash 和 RAM 里。为了搞懂这个,你需要记住几个核心英文单词:
-
Code (代码):你写的所有函数逻辑。
-
RO-data (Read Only) :只读数据(如
const修饰的常量、字符串"Hello"、宏定义的常量表)。 -
RW-data (Read Write) :已初始化的全局/静态变量(比如
int a = 10;)。 -
ZI-data (Zero Init) :未初始化或初始化为 0 的全局/静态变量(比如
int b = 0;或int c;)。
1. Flash (ROM) 被谁占了?
Flash 是存放代码的地方,只要掉电不丢失。
-
Code:所有函数的机器指令。
-
RO-data :所有的常量和字符串(比如你代码里的
"HELLO")。 -
RW-data 的初始值 :变量
a = 10中的那个10,必须存在 Flash 里,否则掉电就忘了。 -
(注意:ZI-data 和真正的 RW-data 变量本体不在 Flash 里)。
2. RAM (SRAM) 被谁占了?
RAM 是程序运行时的工作台,速度极快,但容量很小(通常几 KB 到几百 KB)。
-
栈 (Stack) :存放函数的局部变量、函数调用的返回地址。(如果不加
static,局部变量都在这里)。 -
堆 (Heap) :存放
malloc动态分配的内存。(嵌入式里极度不推荐使用malloc,容易产生碎片)。 -
RW-data 的运行体 :程序刚上电时,会把 Flash 里存的初始值(
10)复制到 RAM 里,让变量a真正拥有实体。所以a既占 Flash,又占 RAM。 -
ZI-data :变量
b本体在 RAM 里,上电时系统直接把这块区域清零(不占 Flash 空间)。
💡 一个经典公式与例子
我们假设定义如下代码:
cpp
const char str[] = "Hello"; // 占 Flash (RO)
int a = 10; // 占 Flash (RW初值) + 占 RAM (RW运行体)
int b; // 占 RAM (ZI)
void main() {
int c = 5; // 占 RAM (栈 Stack)
}
编译后的结果(通常看 .map 文件):
-
占用 Flash (ROM) = Code + RO-data + RW-data (初值)
-
占用 RAM = RW-data (运行体) + ZI-data + Stack + Heap
⚠️ 嵌入式开发中的"血泪"经验(避坑指南)
-
大数组、大字符串,一定要加
const!- 如果你写了
char buf[1024] = ".......";(没加 const),这 1024 个字节不仅要在 Flash 里占 1KB,上电后还会被复制到 RAM 里再占 1KB。很多单片机 RAM 只有 20KB,你搞几个这种数组,程序直接就崩了(HardFault)。加上const后,它只占 Flash,不占 RAM。
- 如果你写了
-
防止栈溢出 (Stack Overflow):
- 不要在函数里定义巨大的局部数组(比如
uint8_t temp[2048];)。局部变量在栈里,单片机的栈通常很小(启动文件里一般配置为 1KB 或 2KB)。定义这么大的局部数组,一运行就把栈冲垮了,导致程序跑飞。巨大的数组一定要放在全局区(或加static)。
- 不要在函数里定义巨大的局部数组(比如
-
慎用
malloc:- 除非你跑 Linux 或者高性能 RTOS,在裸机单片机里,尽量用静态分配 (定义固定大小的全局数组 + 环形缓冲区 FIFO),完全不用
malloc。
- 除非你跑 Linux 或者高性能 RTOS,在裸机单片机里,尽量用静态分配 (定义固定大小的全局数组 + 环形缓冲区 FIFO),完全不用
-
区分
static和const:-
const管的是只读(放 Flash)。 -
static管的是作用域和生命周期(放全局区 RAM/Flash)。 -
如果写
static const uint8_t table[] = {...},它就是一个:只能在当前文件访问的、存在 Flash 里的只读数据(完美!)。
-
掌握了这些,在写代码时,脑子里就会有一张内存地图,知道变量到底去了哪里。下次在 Keil 或 VS Code 编译完成后,可以看看底部的输出信息(比如 Program Size: Code=1234 RO-data=56 RW-data=12 ZI-data=100)