作用域与 static/extern
区分作用域/存储期/链接属性,掌握 static 三种用法与 extern 多文件协作
- 来源
- 补充
标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原
完成标准(本章)
- 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 150 秒
- ✏️ 已练习:小练习正确率 ≥ 60%
- 📝 已通过测验:分数 ≥ 60 分
- 🛠️ 已掌握还需完成实践任务
1.3.5 作用域与 static/extern
本章来源:作用域/生命周期/链接属性与 static、extern 来自《第一阶段讲义》20.5/14.3 节【来源】,经重新组织表述;三概念(作用域/存储期/链接)的分离讲解按 C11 标准 §6.2 重述,多文件实验与工程化建议为新编【补充】并本机实测。平台适用性 universal。
① 学习目标
- 区分三个易混概念:作用域(哪里可见)、存储期(活多久)、链接属性(能否跨文件共享),并分别说出判定规则;
- 说清 static 的三种用法:局部 static(静态存储期、保持值)、全局 static(内部链接、文件私有)、函数 static(内部链接);
- 正确使用 extern 声明跨文件共享变量:声明放 .h、定义只在一处 .c;
- 独立完成三文件工程(static 隔离 + extern 共享)并真实编译链接运行;
- 说出全局变量的工程化建议(少用全局、static 限制可见性、getter 封装)。
② 前置知识
- 必选:1.3.3 函数与递归(函数定义与声明);1.2.2 数据类型(变量定义与存储位置)。
③ 核心概念【来源】
| 概念 | 说明 |
|---|---|
| 作用域(scope) | 标识符在代码里哪里可见:块作用域({} 内)、文件作用域(整个 .c)、函数原型作用域 |
| 存储期(storage duration) | 对象活多久:自动(进块生、出块亡)、静态(程序全程)、线程/分配(后续章节) |
| 链接属性(linkage) | 名字能否跨文件共享:外部链接(extern 全局共享)、内部链接(static 文件私有)、无链接(局部变量) |
| static × 局部变量 | 存储期变静态:只初始化一次、出块不亡、下次进入保持上次的值(链接不变,仍无链接) |
| static × 全局变量/函数 | 链接变内部:文件私有,其他 .c 看不见同名标识符——模块封装利器 |
| extern | 声明"这个变量/函数定义在别处":extern int x; 是声明不分配存储;定义只能出现在一个 .c 里 |
| 头文件放置规则 | 声明入 .h(extern 声明、函数原型、类型),定义只在一处 .c;头文件被多个 .c 包含,放定义必然多重定义(见 ex3 真实报错) |
④ 通俗解释【补充】
- 作用域是"谁能喊到我的名字":在哪个房间(块/文件)里注册的名字,出了房间别人就喊不应;
- 存储期是"我能活多久":自动变量像"临时工",进房间上工、出房间走人;static 局部变量像"长期工",人离开了工位也还在,下次回来接着上次的活干;
- 链接属性是"名片是否对外公开":extern 是公开发名片(全公司可共享);static 是"内部专线"(只在本部门);局部变量连名片都没有;
- static 的两个"身份"别混:加在局部变量上是"改寿命"(静态存储期),加在全局变量/函数上是"改知名度"(内部链接)——一个词、两件事。
⑤ 示例代码
代码示例与验证记录
- examples/ex1-main.c三文件工程:static 内部函数 + 静态局部计数 + extern 共享变量✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex1-counter.c ex1-main.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
tick 1 = 1 tick 2 = 2 tick 3 = 3 global_counter = 100
差异说明:本机真实编译链接 0 警告;add_one 是 static(内部链接,main.c 不可见),count 是 static 局部(保持值 0→1→2→3)
完整源码见 /code 代码示例页
- examples/ex2-static-local.cstatic 局部变量保持值✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex2-static-local.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
next_id: 1001 next_id: 1002 next_id: 1003
差异说明:本机实测 0 警告;id 只初始化一次,三次调用连续递增——静态存储期与作用域无关(变量仍只在 next_id 内可见)
完整源码见 /code 代码示例页
- examples/ex3-dup.h头文件放定义导致多重定义(diagnostic:预期链接失败)诊断示例(预期报错/警告)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex3-a.c ex3-b.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic(预期失败)适用环境:LinuxWindows(MinGW)展开预期输出(实测)
.../ld.exe: ...b.c:(.data+0x0): multiple definition of `x'; ...a.c:(.data+0x0): first defined here collect2.exe: error: ld returned 1 exit status
差异说明:报错文本为 gcc 15.2.0 实测(临时对象文件名与路径随环境不同);修复:头文件只放 extern 声明(见 ex1-shared.h),定义只写在一个 .c
完整源码见 /code 代码示例页
⑥ 编译与运行方法
ex1 是三文件工程,必须三个文件一起编译链接:
gcc ex1-counter.c ex1-main.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic
gcc ex2-static-local.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic本机编译 0 警告,运行输出与示例卡一致。ex3 为 diagnostic 示例(头文件放定义 → 两个 .c 包含 → 多重定义),报错文本为本机 gcc 15.2.0 实测。
⑦ 常见错误
| 症状 | 原因 | 解决 |
|---|---|---|
multiple definition of 'x'; ... first defined here | 把变量定义(如 int x = 1;)放进了头文件,两个 .c 都包含 | 头文件只放 extern int x; 声明;定义只写在一个 .c(ex3 真实报错) |
| 其他文件用不了某函数/变量 | 它被声明为 static(内部链接) | 要共享就去掉 static + 在 .h 放原型;要私有就保持 static |
| 局部变量每次进入函数都重置 | 声明为普通自动变量 | 需要保持值就加 static(注意:这等价于把变量挪到函数外,与作用域无关) |
undefined reference to 'x' | 只写了 extern 声明,没有任何 .c 提供定义 | 在某一个 .c 里写定义 int x = 0; |
| 全局变量满天飞、改一处坏多处 | 缺封装 | 工程化:能不用全局就不用;必须用时用 static 限文件内 + 提供 getter/setter 函数 |
⑧ 小练习
小练习
学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)ex-1-3-5-1.函数内 static int count = 0; 声明后,每次调用该函数时 count 的行为是?(单选)
◌ 未作答ex-1-3-5-2.想让全局变量 x 只在定义它的 .c 文件内可见,应该?(单选)
◌ 未作答ex-1-3-5-3.代码审查:shared.h 里写了 int x = 1;,且 a.c 与 b.c 都 #include "shared.h"。链接时的现象是?(单选)
◌ 未作答
⑨ 章节测验
章节测验
⑩ 实战任务
实践任务
三文件工程练习 static 隔离与 extern 共享
实现一个三文件工程:① 模块 A(counter)用 static 函数做私有实现、static 局部变量计数; ② 一个跨文件共享变量(extern 声明入 .h、定义只在一处 .c);③ main 调用模块 API 并读写共享变量。 编译链接运行通过后,写一句"static 的三种用法"总结。
输入与输出
无程序输入输出。交付物:① 三文件工程(含头文件)源码;② 编译链接与运行输出;③ static 三种用法总结(一句话内分别说明局部/全局/函数 static 的作用)。
功能要求
- 至少 3 个 .c 文件(或 2 个 .c + 头文件)+ 头文件保护
- 模块内私有函数加 static,计数用 static 局部变量
- 共享变量:extern 声明放 .h,定义只在一个 .c
- 编译命令 gcc ... -std=c11 -Wall -Wextra -Wpedantic 多文件一起编译链接
- 写 static 三种用法总结
限制条件
- 实验在专用目录进行;头文件只放声明不放定义
- 不使用全局变量时不要引入多余的全局
验收步骤(自检清单 0/4)
验收标准
- gcc 多文件编译 0 警告、运行输出正确(验收步骤 2/3)
- 总结覆盖局部/全局/函数 static 三种用法(验收步骤 4)
常见失败原因
- 头文件里写了变量定义 → multiple definition
- 共享变量只声明没定义 → undefined reference
- 把模块私有函数误加 extern 或漏加 static
可选扩展
- 给共享变量配 getter/setter 函数,验证封装后 main 不再直接读写全局
- 再加一个模块 B 引入同名 static 函数,验证内部链接互不冲突
完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)
⑪ 面试问题
面试问题
static 在 C 里有哪几种用法?各自的含义是什么?高频C/C++ · medium
要点:三种:static 局部变量(静态存储期、保持值);static 全局变量(内部链接、文件私有);static 函数(内部链接、模块私有)。
static 修饰局部变量:改变存储期——从自动变为静态,只初始化一次,函数退出后值保留,作用域仍是块内; static 修饰全局变量:改变链接属性——从外部链接变为内部链接,只有本文件可见,可用于模块私有状态; static 修饰函数:同样改变链接属性为内部链接,函数只在定义它的文件内可用,是实现模块封装的常规手段。 面试要点:能区分"改存储期"与"改链接属性"两种情况,而不是笼统说"静态的"。
追问:- 追问:一个 static 局部变量的初始化语句执行几次?(一次;语义上在第一次执行到该语句时初始化,之后跳过)
评分要点:- 三种用法全覆盖
- 区分存储期与链接属性
extern 声明和变量定义有什么区别?多文件共享一个全局变量的正确姿势是什么?高频C/C++ · medium
要点:extern 声明不分配存储,只告诉编译器"定义在别处";定义分配存储且只能有一处。正确姿势:头文件放 extern 声明,定义只写在一个 .c。
extern int x; 是声明:引入一个外部链接标识符,不产生存储;int x = 0; 是定义:分配存储并初始化。 多文件共享的工程姿势:shared.h 里写 extern int x;(可被任意多个 .c 包含),只在某一个 .c 里写 int x = 0;。 反例:把定义放进头文件,被两个 .c 包含后链接期报 multiple definition of 'x'(本章 ex3 实测)。 加分:static 与 extern 互补——不想共享的模块内状态用 static 限制可见性,必须共享的才用 extern。
追问:- 追问:函数声明需要 extern 吗?(不需要,函数原型默认外部链接;static 函数才需要特别说明)
评分要点:- 声明 vs 定义
- 头文件放置规则
- 多重定义报错
作用域(scope)和生命周期/存储期(storage duration)有什么区别?举一个两者不一致的例子。高频C/C++ · medium
要点:作用域是标识符可见的代码范围,存储期是对象存活的时长;static 局部变量就是典型:作用域只在函数内,但存储期是整个程序。
作用域回答"在哪里能写出这个名字"(块/文件/函数原型);存储期回答"对象从创建到销毁持续多久" (自动:进入块创建、离开块销毁;静态:程序启动创建、程序结束销毁)。两者正交: 函数内的 static int count 作用域仍是该函数块内(外面访问不到),但存储期是静态的 (函数返回后对象仍存在、值保留)。反之,自动局部变量两者一致:作用域与存储期都限于块。 面试加分:再提分配存储期(malloc)与线程存储期(_Thread_local)作为第三、四种。
追问:- 追问:把 static 局部变量挪到函数外面去,行为上等价吗?(基本等价于改名前缀的全局变量,但可见范围不同——挪出去后整个文件可见)
评分要点:- 两个概念定义准确
- static 局部变量的正交例子
⑫ 延伸阅读
- 《C 程序设计语言(K&R)》4.3/4.4 节 外部变量与 static——只引章节名;
- C11 标准 §6.2.1(作用域)/§6.2.2(链接)/§6.2.4(存储期)——概念权威出处;
- 下一章预告:1.3.6 结构体与位段——用 struct 把"模块"的多个数据打包,配合 static 函数就是封装的起点。
迁移训练(migration training)
作用域/存储期/链接三规则三平台完全一致;嵌入式两个注意点:
| 环节 | 通用 C(本机实测) | 嵌入式 |
|---|---|---|
| static 局部变量 | 保持值 | 同样常用(状态机状态、计数器);注意静态变量占静态存储区 |
| 全局共享 | extern + 定义一处 | 中断与主循环共享变量必须加 volatile 且注意原子性(1.3.11/1.2.2 勘误 err-7.1-30) |
| 工程化 | static 限可见性 | 驱动模块普遍"static 函数 + 少量对外 API" |
不变的:三概念规则、头文件放置纪律;要变的:共享变量在嵌入式里还叠加并发正确性问题。
内容来源映射
| 内容部分 | 资料 | 位置 | 标记 | 说明 |
|---|---|---|---|---|
| 作用域/生命周期/链接属性、static 三种用法、extern 声明与定义 | 第一阶段讲义 | 20.5/14.3 节 | 【来源】 | 正文在原资料基础上重新组织表述,未大段复制原文;三个概念(作用域/存储期/链接)的分离讲解按 C11 标准 §6.2 重述 |
| 多文件实验、全局变量工程化建议、示例代码 | 无 | 【补充】 | 原资料无多文件链接实验;本章示例全部重写并本机实测(gcc 15.2.0) | |
| 小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读 | 无 | 【补充】 | 原资料该章无成体系练习,全部新编 |