static 有三大主要用途:

(图片来源网络,侵删)
- 在函数内部:修饰局部变量,使其生命周期延长至整个程序运行期间,但作用域仍限制在函数内部。
- 在所有函数外部:修饰全局变量/函数,将其作用域限制在当前源文件(.c 文件)内,实现“内部链接”。
- 在结构体(C11标准):修饰结构体成员,使其在所有实例中共享同一份数据,而不是每个实例都有自己的副本。
下面我们通过具体的例子来逐一解释。
修饰局部变量(改变生命周期)
当一个局部变量被 static 修饰时,会发生两件事:
- 生命周期:从程序开始运行时就存在,直到程序结束才销毁,它不再是“随函数调用而生,随函数结束而亡”。
- 作用域:仍然仅限于声明它的函数内部,外部无法访问。
- 初始化:如果没有显式初始化,
static局部变量会被自动初始化为0(对于数值类型)或NULL(对于指针类型)。
经典例子:统计函数被调用的次数
#include <stdio.h>
// 普通局部变量的版本
void count_normal() {
int counter = 0; // 每次调用函数,counter 都被重新创建并初始化为 0
counter++;
printf("Normal call count: %d\n", counter);
}
// static 局部变量的版本
void count_static() {
static int counter = 0; // 程序启动时初始化一次,之后一直存在
counter++;
printf("Static call count: %d\n", counter);
}
int main() {
printf("--- Testing normal variable ---\n");
for (int i = 0; i < 5; i++) {
count_normal(); // 每次调用,count 都从 1 开始
}
printf("\n--- Testing static variable ---\n");
for (int i = 0; i < 5; i++) {
count_static(); // counter 的值在多次调用之间被保留下来
}
return 0;
}
输出结果:

(图片来源网络,侵删)
--- Testing normal variable ---
Normal call count: 1
Normal call count: 1
Normal call count: 1
Normal call count: 1
Normal call count: 1
--- Testing static variable ---
Static call count: 1
Static call count: 2
Static call count: 3
Static call count: 4
Static call count: 5
代码分析:
- 在
count_normal()中,int counter是一个普通的局部变量,每次进入函数,它都在栈上分配内存,并初始化为0,函数执行完毕,内存被释放,所以无论调用多少次,它都只打印1。 - 在
count_static()中,static int counter被存放在了静态存储区(通常是数据段),而不是栈上,它在main函数执行之前(程序加载时)就被初始化为0,并且一直存在,每次调用count_static(),它都能访问到同一个counter变量,因此其值能够累加。
修饰全局变量和函数(改变作用域/链接性)
默认情况下,一个在所有函数之外声明的全局变量或函数是“外部链接”(external linkage)的,这意味着它可以被同一个项目中的其他源文件(通过 extern 声明)访问。
当 static 修饰全局变量或函数时,它会将“外部链接”变为“内部链接”(internal linkage),这意味着这个变量或函数的作用域被限制在当前源文件内,其他文件即使使用 extern 也无法访问它。
主要目的:

(图片来源网络,侵删)
- 防止命名冲突:在大型项目中,不同的文件可能不小心定义了同名的全局变量或函数,使用
static可以将其作用域限定在文件内,避免冲突。 - 封装性:将一些只被单个文件使用的辅助函数或变量隐藏起来,作为文件的“私有”成员,使代码更清晰、更安全。
例子:
假设我们有两个文件,main.c 和 utils.c。
utils.c (工具函数文件)
#include <stdio.h>
// 这是一个只希望被 utils.c 内部使用的函数
// 加上 static,它就成了“私有”函数,外部无法调用
static void helper_function() {
printf("This is a private helper function from utils.c\n");
}
// 这是一个可以被外部调用的公共函数
void public_function() {
printf("This is a public function from utils.c\n");
// 内部可以调用私有函数
helper_function();
}
// 这是一个全局变量,加上 static 后,它只在 utils.c 内部可见
static int internal_data = 100;
main.c (主程序文件)
#include <stdio.h>
// 尝试声明并调用 utils.c 中的函数
// extern void public_function(); // 这个声明是有效的
// extern void helper_function(); // 这个声明会编译警告或错误
// extern int internal_data; // 这个声明会编译警告或错误
void public_function(); // 假设我们从头文件中知道了这个函数的存在
int main() {
// 可以正常调用 public_function
public_function();
// 输出:
// This is a public function from utils.c
// This is a private helper function from utils.c
// 以下代码如果取消注释,会导致编译错误!
// helper_function(); // 错误: 找不到函数 helper_function
// printf("%d\n", internal_data); // 错误: 找不到标识符 internal_data
return 0;
}
编译与链接:
你可能会想,main.c 怎么知道 public_function 的存在?在实际开发中,我们通常会创建一个头文件 utils.h:
utils.h
#ifndef UTILS_H #define UTILS_H // 声明公共函数,但不暴露 private 的内容 void public_function(); #endif
然后在 main.c 中包含 utils.h,在 utils.c 中也包含 utils.h。
修饰结构体成员(C11 新标准)
这是 C11 标准引入的一个新特性,当一个结构体的成员被声明为 static 时,这个成员的所有实例将共享同一份存储空间。
例子:
#include <stdio.h>
// 定义一个结构体
struct SharedCounter {
int instance_id;
// 这个 counter 将被所有 SharedCounter 结构体实例共享
static int counter;
};
// 在所有函数外部定义并初始化静态成员
// 注意:定义时不需要 static 关键字
int struct SharedCounter::counter = 0;
int main() {
struct SharedCounter s1;
struct SharedCounter s2;
s1.instance_id = 1;
s2.instance_id = 2;
// s1 和 s2 修改的是同一个 counter
s1.counter++;
printf("s1.counter = %d\n", s1.counter); // 输出 1
printf("s2.counter = %d\n", s2.counter); // 输出 1 (因为共享)
s2.counter += 10;
printf("s1.counter = %d\n", s1.counter); // 输出 11
printf("s2.counter = %d\n", s2.counter); // 输出 11
return 0;
}
注意:
- 这个用法在很多方面类似于 C++ 类中的静态成员。
- 它在 C 语言中相对较新,可能不是所有编译器都完美支持(尤其是在编译选项开启 C11 标准的情况下)。
- 在更传统的 C89/C99 标准中,如果想实现类似效果,通常会将共享数据放在结构体外部,并通过函数来访问它。
static 位置 |
作用 | 生命周期 | 作用域 | 链接性 | 典型例子 |
|---|---|---|---|---|---|
| 函数内部 (局部变量) | 延长生命周期,保留上一次的值 | 整个程序运行期间 | 仅限函数内部 | 无链接 | 函数调用计数器 |
| 所有函数外部 (全局变量/函数) | 限制作用域,防止命名冲突 | 整个程序运行期间 | 当前源文件 | 内部链接 | 文件级的私有工具函数或变量 |
| 结构体内 (C11) | 成员在所有实例间共享 | 整个程序运行期间 | 取决于成员本身 | 无链接 | 所有实例共享的计数器或配置 |
理解 static 的这三种用法对于编写高效、健壮且可维护的 C 语言代码至关重要。
