15.2 生存期
对象的生存期 (Lifetime) 描述“一个对象在何时存在”。它与“存储期” (Storage duration) 密切相关,但二者不是同一个概念。
1. 生存期的开始与结束
在标准层面,一个对象的生存期大体可以概括为:
- 开始:对象获得存储,并完成必要的初始化之后;
- 结束:对象的存储被释放,或对象由于作用域结束而不再存在之后。
当对象的生存期结束后,再通过指向它的指针去访问该对象,通常是未定义行为 (Undefined Behavior, UB)。
2. 四类存储期 (Storage duration)
每个对象都有存储期。C(C11 起)有四类存储期:
- 自动存储期 (Automatic storage duration):块作用域内、未使用
static的对象,通常在进入块时创建、离开块时销毁。 - 静态存储期 (Static storage duration):文件作用域对象、或块作用域内声明为
static的对象,从程序开始到程序结束都存在。 - 线程存储期 (Thread storage duration):以
_Thread_local(C11)或thread_local(C23)声明的对象,在线程开始到线程结束期间存在。 - 分配存储期 (Allocated storage duration):通过动态内存分配获得的存储(
malloc/free等)。
更偏“声明规则”的角度见:15.3 存储期 链接。
3. 常见例子
3.1 自动对象:离开作用域即结束
c
int* f(void) {
int x = 1;
return &x; // 返回悬垂指针:离开函数后 x 的生存期结束
}1
2
3
4
2
3
4
3.2 静态对象:整个程序期间存在
c
int* g(void) {
static int x = 1;
return &x; // OK:x 具有静态存储期
}1
2
3
4
2
3
4
3.3 字符串字面量:静态存储期
字符串字面量(例如 "abc")对应的存储具有静态存储期。注意:修改字符串字面量的内容是 UB。
3.4 动态分配:malloc 与 free
c
#include <stdlib.h>
int main(void) {
int* p = malloc(sizeof *p);
if (p == NULL) {
return 1;
}
*p = 42;
free(p);
/* 此后 p 指向的存储已被释放,继续解引用是 UB。 */
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
4. 习题
#11513
⚡1⏳2
写一个函数返回自动存储期对象的地址,并解释为什么这是 UB;再写一个修正版本(例如使用 static 或动态分配),并说明两种修正的差异。
#11514
⚡1⏳2
写一个程序:
- 返回一个指向字符串字面量的指针;
- 尝试修改字符串字面量;
- 解释为什么这会触发 UB。
#11515
⚡1⏳2
写一个程序:分配一段动态内存,写入数据后 free,再把指针设为 NULL。解释“设为 NULL”能防止哪一类 bug,防不了哪一类 bug。
对象的创建与结束
存储期决定对象的创建与结束
自动存储期对象通常在进入块时创建、离开块时结束;静态存储期对象贯穿整个程序执行;线程存储期对象与对应线程同寿命;动态分配存储由 malloc 一族函数管理,并通过 free 显式释放。
c
#include <stdio.h>
static int counter = 0;
void step(void) {
int local = 1;
counter += local;
printf("counter = %d\n", counter);
}
int main(void) {
step();
step();
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可能的输出(示例):
bash
counter = 1 counter = 2