CSCI3150 Lab 2 操作教程
本教程配合 逐页伴读,使用 2026-09-24 用户提供的 PDF 与 ZIP。先运行教学示例,理解内存、文件偏移与结构体,再动手写练习。
提交接口已澄清(2026-09-26 收到公告):根据用户提供的公告转贴(本地资料),任意 C 文件 I/O 方法均可,包括 fopen/fscanf/fprintf/fclose 和 open/read/write/close。满足输入输出要求即可;只提交 exercise.c,不修改 Makefile。PDF 与 ZIP 原先各自指定接口的差异已解决。
本文中的容器 ID、环境实测与作答进度是原编写日期的记录;2026-10-13 本轮仅复核学习内容,没有重新启动容器或确认作答进度。执行命令前以当前容器列表及自己的文件为准。
1. 文件已经准备在哪里
| 内容 | 路径或名称 | 用途 |
|---|---|---|
| 原始讲义 | Lab02 PDF(本地课件) | 原字节保存 |
| 原始代码包 | Lab02 ZIP(本地资料) | 原字节保存 |
| Mac 工作目录 | 课程工作目录/CSCI3150-Lab1/Lab02 |
已解压六个原始文件 |
| 容器工作目录 | /work/Lab02 |
与 Mac 上同一份工作文件 |
| 原始练习骨架 | exercise_skeleton.c |
main 函数为空,等待自己实现 |
| 教学示例 | concept_demo.c |
额外编写,可直接运行,非提交作答 |
原代码包只有 exercise_skeleton.c,而 Makefile 依赖的是 exercise.c。这是要从骨架建立工作文件,不是改 Makefile 去迁就骨架文件名。当前没有生成完成的 exercise.c,也没有提交任何文件。
2. 进入现有 Docker
使用 VS Code 时,打开共享根目录 课程工作目录/CSCI3150-Lab1,再选择 Reopen in Container;不要只打开课程原件目录。容器终端执行:
cd /work/Lab02
pwd
ls
如果从 Mac 终端操作,先查看容器:
docker ps -a
本次已核验原有容器 f8d059630b56 正在运行,可进入:
docker exec -it f8d059630b56 bash
cd /work/Lab02
该 ID 是本次观察值;容器重建后按实际列表替换。若它只是停止了,先 docker start f8d059630b56。不要为 Lab2 删除或重建 Lab1/HW01 工作目录。
本次实测 Ubuntu 20.04.6、aarch64(ARM64)、GCC 9.4.0、Make 4.2.1。PDF p.16 要求沿用 Lab1 环境,未新增架构或库要求,所以复用现有环境;这不等于断言评分机器也是 ARM64。
3. 先运行教学示例,观察四件事
在容器终端执行:
cd /work/Lab02
gcc -std=c99 -Wall -Wextra -Werror -pedantic concept_demo.c -o /tmp/lab2-concepts
lab2_demo_dir=$(mktemp -d /tmp/lab2-concepts.XXXXXX)
cd "$lab2_demo_dir"
/tmp/lab2-concepts 6
使用临时工作目录,是因为示例会生成 seek-demo.bin。它不会修改练习的 input.txt 和官方样例。主源码在 concept_demo.c(本地资料)。
实测输出:
sizeof(int)=4 sizeof(pointer)=8
n=6 VLA_bytes=24 heap_bytes=24
0 2 4 6 8 10
after write offset=20
read1=20 text=Hello World!
read2=14 text=World!
bookA=1 bookB=3
按顺序观察:
- 指针和对象大小不同。p 自身是 8 字节,但它指向的六个 int 是 24 字节。不要用 sizeof(p) 代替数组长度。
- 先分配,再初始化,再使用,最后释放。循环填入 i×2;malloc 本身没有生成偶数,也没有清零保证。
- 文件偏移不是内存指针。写 20 字节,偏移到 20;seek 回 0 后读 20;seek 到 6 后只剩 14 字节。World! 的起点是下标 6。
- 值副本与原对象不同。改变副本后 bookA 仍是 1;通过地址修改后 bookB 变成 3。示例给对象补了初值,避免直接照抄原页未初始化对象。
再执行 /tmp/lab2-concepts 3,前面的数组变成 12 字节和 0 2 4;后面的文件与结构体部分不变。这两种参数都已在原容器实际运行,并通过严格编译选项。它验证的是教学例子,不是 exercise.c 的正确性。
4. 看清练习输入与输出
回到工作目录,查看原文件:
cd /work/Lab02
cat README.md
cat exercise_skeleton.c
cat Makefile
cat sample_input.txt
cat sample_output.txt
十行样例的奇数行是 12、8、3、11、9,和为 43;偶数行是 5、20、7、4、6,和为 42。完整逐行表在伴读附录 A2。关键是位置的奇偶,不是整数值的奇偶。
只需“当前读到的数、当前行的奇偶、两个和”。每成功读到一行,更新一个和并切换奇偶;直到 EOF 才输出。不要假定固定十行,不要写死 43 和 42,也不必为所有输入建立一个无限增长数组。
5. 两条接口路线分别怎样理解
| 步骤 | stdio 路线(公告允许) | POSIX 路线(公告允许) |
|---|---|---|
| 输入打开 | fopen 的 r 模式 | open 的 O_RDONLY |
| 读入 | fscanf 成功转换一个整数 | read 获取字节,再拼完整行 |
| EOF | 以转换返回值控制,区分 ferror | 正请求量的 read 返回 0 |
| 数值转换 | fscanf 的格式负责 | 完整行用 sscanf 或其他合适转换 |
| 输出打开 | fopen 的 w 模式 | O_WRONLY、O_CREAT、O_TRUNC 与创建 mode |
| 输出 | fprintf 写两个数与换行 | 格式化文本,write 写足字节 |
| 关闭 | fclose,并检查结果 | close,并处理失败 |
stdio 的 %d 需要整数变量的地址,例如 &value;它返回成功转换项数,不是读入的整数值。不能把 fscanf(...) 返回的 1 当作输入数值累加。该方法依赖题目“一行一个整数、输入有效”的保证;空行等未规定格式不靠它自动保留物理行号。
POSIX 的 read 不懂整数,也不保证停在换行。若块末尾只有字符 1,不能马上当成数值 1;下个块可能以 2 开头,组合后本来应是 12。应保留半行,直到看到行终止符或最后 EOF。write 的返回值也可能小于请求量,需要继续写剩余部分。
两个路线最终算法相同,但文件接口和解析责任不同。任选一条完整路线即可,没有同时使用所有函数的要求。对这个按整数读取并求和的练习,stdio 路线需要手动处理的解析细节更少。
补充:块边界与文本记录边界是两条独立的线。 即使题目保证每行一个合法整数,POSIX read 取得的一块字节也可能只含某个整数的前半段。解析器需要记住“已经完整的记录”与“尚未遇到终止位置的尾段”;下一次读来的内容先接到尾段后,再判断完整记录。不能因为输入保证合法,就把每次 read 的返回范围自动当成一整行。
stdio 路线把部分解析工作交给库,但仍要按成功转换项数推进逻辑计数;只有真正获得一个输入整数,才更新属于该记录的累加状态。失败或 EOF 没有产生新整数,不能再把上一次变量里留下的值算一次。两条路线的共同自查是:每条有效输入记录恰好贡献一次,没有因短读被拆成两条,也没有因退出循环前又处理一次旧值而重复。
6. 建立自己的 exercise.c 与编译
准备写时,在容器终端执行:
cd /work/Lab02
cp -n exercise_skeleton.c exercise.c
-n 避免覆盖已有 exercise.c。完成逻辑后再运行老师的构建命令:
make
./lab02
cat output.txt
Makefile 中实际命令是 gcc -o lab02 exercise.c,所以产物叫 lab02。仅复制空 main 就 make 成功,只能说明骨架可编译,不能说明练习做完;它还不会生成题目要求的结果。
如果更改源码后希望单独看警告,可执行下列检查,不修改 Makefile:
gcc -Wall -Wextra -Wpedantic exercise.c -o /tmp/lab02-review
最后仍通过原 Makefile 验证。不要把另一个源码文件名、教学示例、ZIP 或编译产物当成题目指定的 exercise.c 提交。
7. 用官方样例验证,保留自己的 input.txt
以下步骤在你已经完成 exercise.c、并且 make 成功之后进行;本次尚未执行这个作答验收。
cd /work/Lab02
lab2_check_dir=$(mktemp -d /tmp/lab2-check.XXXXXX)
cp lab02 "$lab2_check_dir/lab02"
cp sample_input.txt "$lab2_check_dir/input.txt"
cp sample_output.txt "$lab2_check_dir/expected.txt"
cd "$lab2_check_dir"
./lab02
cat output.txt
sed 's/\r$//' expected.txt > expected-lf.txt
sed 's/\r$//' output.txt > actual-lf.txt
diff -u expected-lf.txt actual-lf.txt
本 ZIP 的样例行尾是 CRLF。上面仅在比较副本中去掉行末 CR;不改原件、不删除普通空格,也不掩盖额外输出行。diff 无输出且退出码为 0 表示此样例文本匹配。样例通过不等于隐藏测试通过。
若希望检查原始字节,用 od -An -tx1 output.txt。正常的 Linux 两行文本可写为 43\n42\n,对应十六进制 34 33 0a 34 32 0a。不要把字符串结尾的 NUL 字节额外写入结果,也不要输出“odd sum:”之类标签。
从一个失败现象缩小到具体阶段
先确认你运行的可执行文件是本次源码生成的,再确认当前目录中的input.txt是准备测试的那个文件。若输出不对,把过程分成读入、转换、分类、累计、格式化、写出六段:记录实际读到的值及它被判定的行号,能区别“12被拆成1和2”与“12读对了却分进偶数组”。调试信息送往终端错误流或另一个日志,不写进要求严格两行的output.txt。
可以在自己的测试副本中只改变一件事:保留数值顺序但改变read块大小,检验解析是否错误依赖块边界;让输入包含负数,检验符号是否丢失;去掉最后一行末尾换行,检验最后一行是否遗漏。前两项属于实现检查,最后一项应标为健壮性扩展;不要把原题未明确保证的额外格式反过来当成隐藏评分承诺。
资源清理沿打开顺序记账:输入打开失败时没有输入对象可关;输出打开失败时,已经成功打开的输入仍要关闭;写出失败时不能把“创建过output.txt”报告为完成。文件存在、文件长度正确、内容正确和所有写入错误已处理是不同检查。
自测:总和正确,但output.txt多一行调试信息,能否算样例通过?
不能。输出格式也是契约的一部分。先比较两行内容及行尾,再查是否多余字符;不要通过删除所有空白或只抽取数字的比较方式把错误隐藏掉。学习阶段可保留调试,但正式输出必须与题目要求分开。
8. 常见现象该查哪里
| 现象 | 先查 |
|---|---|
| No rule to make target exercise.c | 是否只有 exercise_skeleton.c,还未建立工作文件 |
| 找不到 input.txt | 当前目录是否含输入;运行位置而非源码位置决定相对路径 |
| 输出是按数值奇偶分组 | 分支条件是否错误使用 value 的余数,而非行号或轮换状态 |
| 最后一个数加了两次 | 是否用 feof 预判,再忽略读取结果 |
| read 版本把 12 拆成 1 和 2 | 是否把块边界误作行边界 |
| 输出后面残留旧内容 | 是否缺少截断模式,或误用追加模式 |
| 样例通过是否就完成了 | 两套接口均获公告允许;仍需检查不同输入长度、EOF 处理、按行分组和两行输出,不能只验证给定样例 |
来源与完成状态
考试证据:读了 Lab02-C基础与文件IO-2026T1.pdf(p.15–16 练习题);未识别用途的 PDF:无