Vince Jiang

CSCI3150 / 实验 · 本站更新

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

按顺序观察:

  1. 指针和对象大小不同。p 自身是 8 字节,但它指向的六个 int 是 24 字节。不要用 sizeof(p) 代替数组长度。
  2. 先分配,再初始化,再使用,最后释放。循环填入 i×2;malloc 本身没有生成偶数,也没有清零保证。
  3. 文件偏移不是内存指针。写 20 字节,偏移到 20;seek 回 0 后读 20;seek 到 6 后只剩 14 字节。World! 的起点是下标 6。
  4. 值副本与原对象不同。改变副本后 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:无