C语言的存储期(Storage Duration) 是对象(变量)生命周期的核心属性,它决定了对象从创建到销毁的时间段。
C语言标准(C11/C17/C23)明确定义了四种存储期:
| 存储期(Storage Duration) | 生命周期开始时间 | 生命周期结束时间 | 典型声明方式 | 常见例子 | 内存位置(通常) |
|---|---|---|---|---|---|
| static | 程序启动前 | 程序终止后 | 文件作用域变量static 修饰的变量 | 全局变量、文件作用域 static 变量、函数内 static 局部变量 | 数据段 / BSS段 |
| thread(C11起) | 线程启动时 | 线程终止时 | thread_local 变量(可与 static/extern 组合) | 每个线程独立的计数器、全局变量副本 | 线程本地存储(TLS) |
| automatic | 进入声明所在的块(block)时 | 离开该块时 | 函数内普通局部变量(默认)auto(C11前可显式写) | 普通局部变量、函数参数 | 栈(Stack) |
| allocated(动态分配) | 调用 malloc/calloc/realloc 成功时 | 调用 free 或程序结束(泄漏) | 通过 malloc 等分配的内存 | 动态数组、链表节点、对象池 | 堆(Heap) |
1. static storage duration(静态存储期)
特点:
- 程序整个运行期间都存在
- 只在程序启动前初始化一次(若未显式初始化,则自动清零)
- 同一对象在整个程序中只有一份
典型声明方式:
// 文件作用域(全局)变量,默认就是 static storage duration
int global_var = 10;
// 显式 static(文件作用域时相当于给 internal linkage)
static int file_static = 20;
// 函数内部的 static 变量(仍然是 static storage duration)
void func() {
static int counter = 0; // 只初始化一次
counter++;
printf("%d\n", counter);
}
2. thread storage duration(线程存储期)—— C11 新增
特点:
- 每个线程拥有自己独立的实例
- 线程开始时分配,线程结束时销毁
- 初始化在第一次使用时进行(类似函数内 static 的行为)
使用方式(需要包含 <threads.h> 或使用 C11+ 编译器):
#include <threads.h>
#include <stdio.h>
thread_local int tls_counter = 0; // 每个线程有自己的 tls_counter
int thread_func(void* arg) {
tls_counter++;
printf("Thread %d: tls_counter = %d\n", thrd_current(), tls_counter);
return 0;
}
注意:thread_local 可以和 static 或 extern 一起使用,但不能和 auto/register 一起使用。
3. automatic storage duration(自动存储期)
特点:
- 每次进入块(block)时分配
- 离开块时销毁(栈上变量出栈)
- 未初始化时值不确定(可能是垃圾值)
- 大多数局部变量都属于这一类
典型例子:
void func(int param) { // param 是 automatic
int x; // automatic,未初始化 → 垃圾值
auto int y = 10; // C11 前可写 auto,C11 后基本不用写
{
int z = 30; // 新的 automatic 变量
} // z 在此处销毁
} // x, param, y 在函数结束时销毁
4. allocated storage duration(分配存储期 / 动态存储期)
特点:
- 由程序员手动控制(
malloc/calloc/realloc创建,free销毁) - 生命周期与调用
free相关,与任何块或线程无关 - 未释放会导致内存泄漏
- 分配失败返回
NULL
典型用法:
#include <stdlib.h>
int* create_array(int size) {
int* p = malloc(size * sizeof(int)); // allocated storage
if (p == NULL) return NULL;
// ... 使用 p ...
return p; // 记得在外面 free
}
// 使用
int* arr = create_array(100);
if (arr) {
// 使用...
free(arr); // 必须释放
arr = NULL; // 好习惯
}
常见问题与面试高频对比
| 问题 | static 存储期 | automatic 存储期 | thread 存储期 |
|---|---|---|---|
| 生命周期 | 整个程序 | 当前块 | 当前线程 |
| 初始化次数 | 一次 | 每次进入块 | 每个线程一次 |
| 默认初始化值 | 0(全局/静态) | 不确定(垃圾值) | 0(C11规定) |
| 典型内存位置 | 数据段/BSS | 栈 | 线程本地存储(TLS) |
| 能否取地址(&) | 能 | 能 | 能 |
| 多线程下是否安全 | 所有线程共享(需加锁) | 每个函数调用栈独立 | 天然线程安全(每个线程一份) |
函数内声明 static int x; 的行为 | 整个程序一份,保持值 | — | — |
函数内声明 thread_local int x; | 每个线程一份,保持值 | — | — |
小结表格(便于记忆)
| 存储期 | 关键字 | 存在时间 | 典型场景 | 是否共享 |
|---|---|---|---|---|
| static | 无(全局)/ static | 程序全程 | 全局变量、计数器、配置 | 是 |
| thread | thread_local | 线程全程 | 每个线程独立的全局变量 | 否 |
| automatic | 无(局部)/ auto | 块作用域 | 普通局部变量、循环变量 | 否 |
| allocated | 无(malloc 等分配) | 手动 free 前 | 动态数据结构(链表、树等) | 是(需同步) |
希望这篇笔记能帮你把 C 语言中“变量能活多久”这个核心概念理清楚!
有哪部分还想再深入(比如 static 在多文件中的 linkage 行为、线程局部变量的初始化细节、VLA 与存储期关系等)可以继续问~