6. static 关键字的三种作用:局部变量、全局变量、函数

说实话,static 这个关键字,我见过太多人栽跟头了。

刚入行那会儿,我自己也迷糊过——明明加了 static,怎么这个变量就“变了个样”?后来在嵌入式项目里调试一个串口通信的 bug,折腾了两天,最后发现就是一个 static 局部变量没理解透。嗯,从那以后,我对 static 算是彻底搞明白了。

今天咱们就把 static 的三种用法掰开揉碎,讲清楚。

6.1 static 修饰局部变量

先看最常见的用法——修饰局部变量。

普通局部变量,每次进入函数都会重新分配内存,函数退出就释放。但加上 static 之后,它的生命周期就变成了整个程序运行期间。说白了,它只初始化一次,之后每次调用函数,它都保留着上一次的值。

#include <stdio.h>

void counter() {
    static int count = 0;  // 只初始化一次
    count++;
    printf("count = %d\n", count);
}

int main() {
    counter();  // 输出 1
    counter();  // 输出 2
    counter();  // 输出 3
    return 0;
}

你看,count 的值在每次调用后都保留了下来。普通局部变量做不到这一点。

我的经验:在嵌入式开发中,我经常用 static 局部变量来记录函数被调用的次数,或者保存一些中间状态。比如按键消抖处理,就需要记住上一次的电平状态——用 static 最合适。
注意:static 局部变量虽然生命周期长,但它的作用域没变——仍然只在定义它的函数内部可见。你没法在函数外面直接访问它。

6.2 static 修饰全局变量

全局变量默认是“外部链接属性”——也就是说,你在一个 .c 文件里定义的全局变量,其他 .c 文件用 extern 声明一下就能用。

但加上 static 之后,这个全局变量就变成了“内部链接属性”。它的作用域被限制在了当前文件内,其他文件无法访问。

// file1.c
static int secret = 42;  // 只在 file1.c 中可见

void show() {
    printf("%d\n", secret);
}

// file2.c
extern int secret;  // 错误!无法访问 file1.c 中的 static 变量

为什么会这样?说白了,static 让这个变量“私有化”了。你想想看,在大型项目中,如果每个文件都随意暴露全局变量,命名冲突和意外修改的问题会让人崩溃。

核心理解:static 全局变量 = 文件级别的“私有变量”。它解决了多文件协作时的命名空间污染问题。

我在做嵌入式项目时,习惯把硬件寄存器地址、模块内部状态这些“不想让别人碰”的东西,统统加上 static。这样即使团队里有人不小心写了同名变量,也不会出问题。

6.3 static 修饰函数

static 修饰函数,效果和修饰全局变量类似——把函数的作用域限制在当前文件内。

// helper.c
static int add(int a, int b) {
    return a + b;
}

int public_add(int a, int b) {
    return add(a, b);  // 内部调用没问题
}

// main.c
extern int add(int a, int b);  // 错误!无法访问 static 函数

static 函数也叫“内部函数”或“文件作用域函数”。它只在本文件中可见,其他文件即使声明了 extern 也链接不上。

我的习惯:在写模块时,我会把对外暴露的接口函数不加 static,而内部使用的辅助函数全部加上 static。这样接口清晰,别人一看就知道哪些函数是“公开的”,哪些是“内部实现细节”。

我曾经在一个通信协议栈的项目里,把所有底层字节处理函数都加了 static。后来新同事接手代码,直接说“这代码结构真清爽”——其实只是用了 static 把公共接口和内部实现分开了而已。

6.4 三种作用对比

修饰对象 作用 生命周期 作用域 链接属性
局部变量 延长生命周期,保留值 程序运行期间 函数内部 无(局部)
全局变量 限制作用域到当前文件 程序运行期间 当前文件 内部链接
函数 限制作用域到当前文件 程序运行期间 当前文件 内部链接

6.5 知识体系结构图

static 关键字三种作用 static 修饰局部变量 修饰全局变量 修饰函数 生命周期延长 作用域不变 作用域限制到 当前文件 作用域限制到 当前文件 核心:static = 控制作用域 + 控制生命周期 局部变量:延长生命周期 | 全局变量/函数:缩小作用域

6.6 避坑指南

我总结几个容易踩的坑,都是血泪教训:

  • static 局部变量不自动清零——它只在第一次调用时初始化一次。如果你指望每次进函数都重置为0,那就错了。
  • static 变量不能作为函数参数——嗯,这个其实不是语法限制,而是语义问题。static 变量是“有记忆”的,传参进去反而容易让人困惑。
  • 多线程环境下要小心——static 变量是共享的,多个线程同时读写就可能出问题。我在一个 RTOS 项目里就踩过这个坑,后来加了互斥锁才解决。
我曾经犯过的错:在一个中断服务函数里用了 static 变量来计数,结果主循环和中断同时访问它,导致计数乱跳。后来才意识到——static 变量不是原子操作,多线程/中断环境下需要保护。

好了,static 的三种作用就讲到这里。记住一句话:static 的本质就是“控制可见性和生命周期”。局部变量用它来“续命”,全局变量和函数用它来“藏起来”。理解了这个,你就不会再搞混了。


公众号:蓝海资料掘金营,微信 deep3321