Tips1
全局变量和静态变量的生命周期完全相同,都是"整个程序运行期间" ;它们的真正区别在于作用域(可见范围),而不是存活时间。
生命周期对比
| 类型 | 生命周期 | 说明 |
|---|---|---|
| 普通全局变量 | 程序开始 → 程序结束 | 程序启动时创建,结束时销毁 |
| 静态全局变量 | 程序开始 → 程序结束 | 生命周期与普通全局变量完全一样 |
| 静态局部变量 | 程序开始 → 程序结束 | 第一次进入函数时初始化,之后一直保持 |
三者都存放在RAM静态存储区 (.data 或 .bss 段),不会像局部变量那样在函数返回时被释放。
真正区别在"作用域"
- 普通全局变量 :整个程序可见,其他
.c文件可以用extern访问。 - 静态全局变量 :只在定义它的那个
.c文件内可见,其他文件无法访问。 - 静态局部变量:只在定义它的函数内可见,但函数返回后值不会丢失,下次调用仍保留上次的值。
一句话记
static 放在全局变量前,是"缩小可见范围";放在局部变量前,是"延长存活时间"。 但无论哪种静态变量,生命周期都和全局变量一样,贯穿整个程序运行期。
Tips2
const 在 C 语言里不是"常量定义器",而是只读限定符:它告诉编译器,这个对象不能通过这个标识符被修改。
1. 修饰普通变量
c
const int a = 10;
int const b = 20; // 和上面等价
- 必须在定义时初始化。
- 之后不能赋值、不能
a++。 - 在 C 语言里,
const int a仍是只读变量 ,不能当编译期常量用,比如不能写成int arr[a];。
2. 修饰指针:看 const 在 * 的哪一侧
| 写法 | 含义 | 能否改 |
|---|---|---|
const int *p / int const *p |
指向常量的指针 | 不能改 *p,可以改 p |
int * const p |
常量指针 | 不能改 p,可以改 *p |
const int * const p |
指向常量的常量指针 | p 和 *p 都不能改 |
记忆方法:const 靠近谁,谁就不可改。
c
int x = 10, y = 20;
const int *p1 = &x; // 不能 *p1 = 30;可以 p1 = &y
int * const p2 = &x; // 可以 *p2 = 30;不能 p2 = &y
const int * const p3 = &x; // 都不能改
3. 修饰数组
c
const int arr[3] = {1, 2, 3};
arr[0] = 10; // 错误:元素只读
函数参数里也常用:
c
void printArray(const int arr[], int size) {
// arr[i] = 0; // 错误:保护数组内容不被修改
}
这里 const int arr[] 本质上等价于 const int *arr。
4. 修饰函数参数和返回值
修饰参数,主要是保护传入的数据:
c
void printStr(const char *str) {
// *str = 'A'; // 错误:不能修改传入字符串内容
}
修饰返回值,通常用于返回指针时,防止调用者直接修改:
c
const char *getName(void);
5. 嵌入式里两个容易踩的点
const全局数组、表、字库等,常被放在 Flash/ROM 里,不占 RAM。- 如果是硬件寄存器,只读但可能被硬件改变,通常写成:
c
volatile const uint32_t *reg = (volatile const uint32_t *)0x40000000;
const 表示程序不该写它,volatile 表示值可能被外部改变。
一句话记
const 修饰的是"通过这个名字不能改",不是"物理上永远不能改"。 指针场景里,只要抓住"const 靠近谁,谁只读",基本就不会乱。
Tips3
可以按"编译器段名 / 加载视图 / 运行视图 / 堆栈"四层来理解,这样不容易混。
1. 先分清两套叫法
| 编译器输出 | 对应段名 | 含义 |
|---|---|---|
| Code | .text |
程序指令 |
| RO-data | .rodata |
只读数据,如 const 全局量、字符串常量 |
| RW-data | .data |
已初始化且初值非 0 的全局/静态变量 |
| ZI-data | .bss |
未初始化或初始化为 0 的全局/静态变量 |
| Stack | --- | 局部变量、函数参数、返回地址 |
| Heap | --- | malloc 动态分配 |
前四个是编译后静态段 ,Stack 和 Heap 是运行时的 RAM 区域。
2. 加载时:程序存在 Flash/ROM 里的样子
程序还没运行、只是烧进 Flash 时:
text
Flash/ROM = Code + RO-data + RW-data
- Code:机器指令。
- RO-data:只读数据,运行后也不复制到 RAM。
- RW-data 的初始值:必须存在 Flash 里,否则掉电后初值就丢了。
- ZI-data 不占 Flash:因为它的初值就是 0,上电后清零即可,没必要在 Flash 里存一份。
3. 运行时:程序在 RAM 里的样子
上电后,启动代码会做两件事:
- 把 RW-data 的初始值从 Flash 复制到 RAM;
- 把 ZI-data 区域全部清零。
所以运行时的 RAM 占用是:
text
RAM = RW-data + ZI-data + Stack + Heap
其中编译器统计的:
text
RW Size = RW-data + ZI-data
表示静态变量占用的 RAM;而 Stack 和 Heap 通常也归在 ZI-data 区域统计里,但它们的大小是启动文件里预留的,真正使用多少要运行时才知道。
4. 每个段对应哪些 C 语言对象
| 段 | 存放内容 | 是否掉电保存 |
|---|---|---|
| Code | 函数代码、指令 | 是,Flash |
| RO-data | const 全局/静态变量、字符串常量 |
是,Flash |
| RW-data | int g = 10; 这类非 0 初值全局/静态变量 |
初值在 Flash,运行在 RAM |
| ZI-data / .bss | int g;、int g = 0; 这类全局/静态变量 |
否,运行在 RAM |
| Stack | 局部变量、函数参数、返回地址 | 否 |
| Heap | malloc 分配出来的内存 |
否 |
5. 一句话记
Code 和 RO-data 常驻 Flash;RW-data 的初值存在 Flash,运行时搬到 RAM;ZI-data 只占 RAM,上电清零;Stack 和 Heap 是运行时在 RAM 里动态使用的区域,通常也归入 ZI-data 统计。
在单片机里,如果看到编译输出里 RW-data 很大,通常说明全局/静态变量初值表占得多;如果 ZI-data 很大,通常是未初始化/零初始化全局变量、数组、栈或堆预留比较多。