5.3 C老头儿和Java小子的硬盘夜话
从语言差异被简化为文件扩展名走到编译、运行时、内存和持久化的可验证边界,沿 C 与 Java 的不同路径重放状态。
学习目标
- 能沿源代码、编译或链接、运行时装载、内存生命周期和磁盘持久化比较 C 与 Java 的执行路径
- 能解释本机程序、类文件、JVM、堆对象和文件状态分别属于哪个生命周期边界
- 能在正常、边界和故障场景中回答:为什么进程退出后,堆里的对象不能替代显式持久化
为什么需要这一机制
本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 5.3 C老头儿和Java小子的硬盘夜话。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。
5.3 C老头儿和Java小子的硬盘夜话 不能停在“C 和 Java 只是扩展名不同”。真实系统要解决的变化是从 语言差异被简化为文件扩展名 到 编译、运行时、内存和持久化分别比较;C 常经过编译链接生成本机程序,显式处理内存,Java 生成类文件并由 JVM 装载执行,对象由运行时管理。两条路径最终都要通过操作系统执行磁盘 I/O,内存状态本身不会自动变成文件。
三个会让语言比较失真的陷阱
一个目录节点到运行时证据
5.3 C老头儿和Java小子的硬盘夜话
在 ↡沿编译、运行时、内存生命周期和系统 I/O 比较 C 与 Java 执行路径,并用可重放文件验证状态边界的机制。 中,运行时映像可以先写成
它提醒我们区分代码、静态数据、堆和栈的生命周期。公式不表示堆会自动写入磁盘;要让状态跨越进程退出,必须增加显式序列化和文件写入步骤。
源代码
↡以 C 或 Java 语义表达输入、状态变化和输出合同的文本阶段。先固定输入、版本和预期状态。源文件只说明意图,不能单独证明编译、链接或运行成功。
编译/链接
↡把源代码转换为本机程序或类文件,并解析依赖与生成物边界的构建阶段。C 的编译与链接通常生成面向目标平台的本机程序;Java 的编译先生成类文件。应保存命令、工具版本、依赖和生成物校验和。
运行时装载
↡把本机程序或类文件装入执行环境,解析运行时依赖并准备执行的阶段。本机程序依赖平台装载器与动态库,Java 依赖 JVM 和类加载过程。装载成功不等于业务状态已经持久化。
内存生命周期
↡记录代码执行期间静态数据、堆对象、栈帧及其分配、回收和退出边界的阶段。C 常需要显式管理分配与释放,Java 对象由垃圾回收等运行时机制管理。两者都不能让进程退出后的内存自动成为可重放文件。
磁盘持久化
↡把经过编码和版本约束的业务状态通过系统 I/O 写入文件,并能在重启后验证和重放的阶段。只有显式写入、刷新并验证的文件才是持久化证据。文件格式、部分写入、权限、崩溃时序和校验和都应记录。
最小可重放实现
input = fixed_case()
artifact = build(source, compiler_version)
runtime = load(artifact, runtime_version)
memory = execute(runtime, input)
persist(encode(memory, schema_version), file_path)
assert replay(file_path) == expected(input)这段实现草图只表达 C 与 Java 的状态边界,不复制原书叙事或代码。实际运行时应保存输入、编译或链接命令、生成物、运行时版本、内存观察、文件校验和与重启结果,使另一位读者能够从干净状态重放。
五步复核一次硬盘夜话
1. 固定源代码、输入与预期
记录语言、版本、源文件、输入、输出合同和预期内存状态。先预测生成程序或类文件会是什么,再运行并保存基线。
Lab
C 与 Java 的运行时和磁盘持久化实验
先预测状态会停在构建、内存还是磁盘,再切换本机路径、JVM 路径与堆丢失样本。
编译链接生成本机程序,显式分配内存并写入文件
source=c → build=native → memory=explicit → io=write → file=replayable
判定
accept:生成物、内存和文件边界可追踪
当前样本:C 本机路径;保存源文件、生成物、运行时版本、堆快照、文件校验和与复位轨迹。
正常、边界与故障证据矩阵
| 样本 | 只改变的变量 | 预期判定 | 必存证据 |
|---|---|---|---|
| 正常 | 源码、版本、输入和文件权限固定 | 生成物可装载,文件可重放 | 命令、版本、内存状态、文件校验和 |
| 边界 | 一个工具版本、内存峰值或写入时序改变 | 失败边界可解释,状态不被混淆 | 生成物、堆峰值、写入时序、错误 |
| 故障 | 进程退出前不写文件,或文件部分写入 | 拒绝把堆对象当持久化状态 | 退出事件、文件大小、校验和、重启结果 |
故障诊断:先分开构建、内存与磁盘
- 构建侧:查源文件、编译器或 JVM 工具版本、依赖、命令和生成物校验和;先确认程序真的被构建出来。
- 装载侧:查本机库、类路径、JVM 版本和装载日志;构建成功不代表运行时可以装载。
- 内存侧:查堆、栈、静态数据、分配与回收或释放;进程内状态要和文件状态分开命名。
- 磁盘侧:查编码、写入、刷新、权限、部分文件、校验和和重启重放;只有跨进程验证成功才算持久化。
如果 C 与 Java 的最终输出不同,先比较生成物和运行时版本;如果重启后状态丢失,查是否真的写过文件;如果文件存在但不可读,查格式版本、部分写入和校验和。每次只改变一个控制量,并重放基线。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 5.3 C老头儿和Java小子的硬盘夜话
沿编译、运行时、内存生命周期和系统 I/O 比较 C 与 Java 并验证持久化边界的机制。
- 源代码
以 C 或 Java 语义表达输入、状态变化和输出合同的文本阶段。
- 编译/链接
生成面向目标平台的本机程序或类文件并解析依赖的构建阶段。
- 运行时装载
把生成物装入本机执行环境或 JVM 并准备执行的阶段。
- 内存生命周期
记录静态数据、堆对象和栈帧的分配、回收或释放与退出边界的阶段。
- 磁盘持久化
把编码后的状态写入文件并在重启后验证和重放的阶段。
练习
练习
问题 1(5.3 C老头儿和Java小子的硬盘夜话): 为什么不能用 .c 或 .java 的文件扩展名判断程序已经可以运行并且状态已经持久化?
问题 2(源代码、编译/链接、运行时装载): C 的本机程序路径和 Java 的类文件加 JVM 路径,比较实验至少应固定和测量什么?
问题 3(内存生命周期、磁盘持久化): 进程退出后对象丢失,但磁盘上有文件,如何判断这次状态能否恢复?
本页小结
5.3 C老头儿和Java小子的硬盘夜话 的关键不是记住两个扩展名,而是把源代码、生成物、运行时、内存和磁盘状态放进同一条证据链。完成标准是能定位最早偏离,证明堆对象不等于文件,并在重启后用同一输入重放出可解释的状态。