跳到主要内容
🔍
1.3.5已发布intermediate · 约 2 课时 · P0

作用域与 static/extern

区分作用域/存储期/链接属性,掌握 static 三种用法与 extern 多文件协作

  • 来源
  • 补充

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 150
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.3.5 作用域与 static/extern

本章来源:作用域/生命周期/链接属性与 static、extern 来自《第一阶段讲义》20.5/14.3 节【来源】,经重新组织表述;三概念(作用域/存储期/链接)的分离讲解按 C11 标准 §6.2 重述,多文件实验与工程化建议为新编【补充】并本机实测。平台适用性 universal。

① 学习目标

  1. 区分三个易混概念:作用域(哪里可见)、存储期(活多久)、链接属性(能否跨文件共享),并分别说出判定规则;
  2. 说清 static 的三种用法:局部 static(静态存储期、保持值)、全局 static(内部链接、文件私有)、函数 static(内部链接);
  3. 正确使用 extern 声明跨文件共享变量:声明放 .h、定义只在一处 .c;
  4. 独立完成三文件工程(static 隔离 + extern 共享)并真实编译链接运行;
  5. 说出全局变量的工程化建议(少用全局、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"。链接时的现象是?(单选)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

三文件工程练习 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)
小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读补充原资料该章无成体系练习,全部新编