Skip to content

作用域、存储期与 static:名字看得见、变量活得久 ​

引言:两个常被搅混的概念 ​

“这个变量在哪能用?”和“这个变量活多久?”是两个不同的问题,但新手经常把它们搅成一团。C 用两个独立的概念分别回答:**作用域(scope)**回答“名字在哪段代码里能被看见”、存储期(storage duration)回答“变量占的这块内存活多久”。而 static 这个关键字之所以让人迷糊,就是因为它同时影响这两个概念、而且在“局部”和“文件作用域”两种位置上效果不一样。这一章咱们把作用域、存储期、static、extern 一次讲清楚。

作用域:名字的可见范围 ​

作用域规定“一个名字从哪到哪能被引用”。C 有四种作用域(ISO/IEC 9899 §6.2.1),最常用的是块作用域和文件作用域。块作用域就是“在一对花括号 {} 内声明的名字,只在这对花括号里可见”;文件作用域是”在所有函数之外声明的名字(全局变量),从声明处到文件末尾都可见“。咱们真跑一个块作用域的经典现象,内层块的同名变量”隐藏“外层的:

C
#include <stdio.h>

int global = 100; /* 文件作用域:整个文件可见 */

int main(void) {
    int x = 1; /* main 块作用域 */
    {
        int x = 2; /* 内层块:这个 x 隐藏外层的 x */
        printf("块内 x = %d\n", x);
    }
    printf("外层 x = %d, global = %d\n", x, global);
    return 0;
}
终端
$ gcc -std=c11 -Wall scope.c -o sp && ./sp
块内 x = 2
外层 x = 1, global = 100

内层 { int x = 2; } 声明了一个新的 x,它隐藏了外层的 x——所以在内层块里打印 x 得 2;一出内层块,内层那个 x 就“没了”,外层的 x(值为 1)重新可见。global 在函数外声明、是文件作用域,函数里随时能用(得 100)。这种“内层隐藏外层”是合法的,但写同名变量隐藏外层是坏习惯(咱们读代码时容易搞混“这个 x 到底是哪个”),除非有特别理由,别这么干。要避免“名字冲突”,给变量起有区分度的名字比靠作用域隐藏靠谱。

剩下两种作用域出场少得多。函数作用域只有“语句标号”(给 goto 跳的 label:)才有,整个函数内有效。原型作用域是函数原型参数列表里的名字(int f(int x); 里的 x),只在那个原型里有效(其实您完全可以写 int f(int); 不写名字)。

存储期:变量活多久 ​

存储期规定“变量占的那块内存什么时候分配、什么时候释放”(§6.2.4)。C 有四种。自动存储期:普通的局部变量,进函数/块时在栈上分配、出了就回收,这是默认的、最常见的。静态存储期:全局变量、static 局部变量,它们在程序启动时就分配好(落在 .data/.bss 段)、一直活到程序结束,所以它们的值能跨函数调用保持(第 8 章那个 static int count 就是)。动态存储期:靠 malloc/free(指针与内存那一程)手动管理的内存,您决定它什么时候分配、什么时候释放,堆上。线程存储期(C11 的 _Thread_local):每个线程一份,多线程时用。

咱们要抓的关键区分:作用域是“编译期”的概念(编译器检查名字可见性)、存储期是“运行期”的概念(程序运行时内存的生命周期)。一个变量可以“作用域很小、但活得很久”(static 局部:只在函数内可见、但活到程序结束),也可以“作用域很大、活得短”(没有这种合法组合,但理解两者独立就行)。

static 的三重含义 ​

static 是这一章的主角,它根据用在哪、效果不同,所以咱们要分位置记:

用在局部变量上(函数体内):改变的是存储期——从“自动”变成“静态”,变量只在程序启动时初始化一次、之后值跨调用保持(第 8 章咱们已演示 counter() 连调得 1/2/3)。作用域不变(还是只有这个函数能看见)。

用在文件作用域的变量上(函数外的全局变量)、或用在函数上:改变的是链接性——从“external(外部链接,别的翻译单元能引用)”变成“internal(内部链接,只在本翻译单元可见)”。第 1 章咱们已经用 nm 真跑过这个对比:static int secret 在 nm 里是小写 d(internal,只在本文件)、而普通 int visible 是大写 D(external,对外可见)。给函数加 static 同理,这个函数变成“模块私有”,别的 .c 文件链接时看不到它、也调不到。

所以记的时候分位置:static 在局部改“活多久”(存储期)、在全局/函数改“谁看得见”(链接性)。工程里咱们常用“文件作用域 static 函数/变量”来实现模块的“私有”辅助函数和内部状态,对外只暴露那些没加 static 的“公开接口”,把实现细节藏起来,这是 C 模块化的基本手法(C 没有 private 关键字,static 就是它的私有机制)。

extern:引用别处定义的变量 ​

跟 static 相对的是 extern:它声明“这个变量在别处定义、这里只是引用它的类型”,本身不分配存储。咱们看第 1 章那个 counter.h 里的 extern int counter; 就是——counter 的定义在 counter.c 里,别的翻译单元通过这个 extern 声明知道“有这么个 int counter”、链接时解析到定义。函数原型本身就有”extern“的含义(函数默认 external 链接),所以函数声明不用写 extern(写了也对、但多余)。

小结 ​

作用域(§6.2.1,编译期“名字在哪可见”)和存储期(§6.2.4,运行期“内存活多久”)是两个独立的概念。作用域主要是块作用域({} 内)和文件作用域(全局);内层块的同名变量会隐藏外层(真跑块内 x=2、外层 x=1),但写同名隐藏是坏习惯。存储期有四种:自动(栈上局部,默认)、静态(全局/static 局部,程序全程,落在 .data/.bss)、动态(malloc/free,指针与内存那一程)、线程(_Thread_local)。static 三重含义:用在局部变量改存储期(变静态、跨调用保持,第 8 章)、用在文件作用域变量/函数改链接性(变 internal、只在本翻译单元,第 1 章 nm 小写 d/t)——口诀“局部改活多久、全局改谁看得见”,它是 C 模块化的“私有”机制。extern 反过来,声明“别处定义、这里引用”,不分配存储。下一章咱们看数组。

参考资源 ​

  • ISO/IEC 9899:2011 §6.2.1(作用域:块/文件/函数/原型)、§6.2.4(存储期:自动/静态/线程/动态)、§6.2.2(链接性:external/internal/none)
  • 第 1 章:程序结构与编译四阶段(链接 + nm 大小写、extern)、第 8 章:函数(static 局部变量的存储期)
  • 指针与内存卷(动态存储期 malloc/free)

87bb5f2 · 87bb5f2 · 2026-09-21