前置知识
本文改编自 MaxXing 编写的《编译原理》实践文档相关章节。
本节介绍了完成《软件设计实践》大作业所需的部分前置知识。如果你对此并不了解,建议你先花一些时间进行学习。
C++ 面向对象风格编程
你将在本项目中充分运用前半学期所学到的面向对象编程知识。如果你对这方面的知识并不熟练,你可以时刻复习相关的课件。
Lisp
你需要了解 Lisp 这种编程语言。如果你还没有了解的话,可以读一下 30 分钟 Lisp 入门教程。
Git(不做要求)
Git 是一个版本控制系统 (version control system,VCS)。什么是版本控制?为什么需要做版本控制?
想象你正在开发你的解释器,你决定给你的解释器实现一个新的功能,但添加这个功能需要修改大量之前的代码。你一咬牙一狠心,熬了个大夜,终于把这个功能加完了,遇到的问题也都修好了。你信心满满地打开 OJ 交了一发:
“tmd,一个测试用例都没过。”
你坐在宿舍的椅子上,窗户灌进阵阵冷风,看着 WA 声一片的提交界面,你的心早就凉了。
“要是能回到加这个功能之前就好了。” 你想着。但你没有粉色大猫猫,也没有 SERN 的 LHC (和助手),更没有时之盾牌,那些过去的时间再也回不去了。
不过,不幸中的万幸,你用 Git 管理了你的代码。
你熟练地在命令行里敲下 git reset xxx
,瞬间,一切回到了那天之前。
一些你需要知道的基本内容:
- 初始化 Git 仓库: 在仓库目录中
git init
。 - 忽略部分文件的更改: 在对应目录中放置
.gitignore
文件,并在该文件中添加需要忽略的文件的规则。 - 查看仓库状态:
git status
。 - 暂存更改:
git add 文件名
,或git add -A
暂存全部更改。 - 提交更改:
git commit
,此时会弹出默认编辑器并要求你输入提交信息。也可以直接执行git commit -m "提交信息"
。 - 添加远程仓库:
git remote add 名称 仓库URL
。 - 推送本地提交到远程:
git push
。 - 查看所有提交记录:
git log
,你可以从中看到某个提交的哈希值。 - 把仓库复位到某个提交的状态:
git reset 提交的哈希值
。 - 从当前提交新建分支并切换:
git checkout -b 分支名
。 - 切换到分支:
git checkout 分支名
。 - 删除分支:
git branch -D 分支名
。
上面提到了 .gitignore
可以让 Git 忽略目录中某些文件,且不让它们出现在 Git 仓库中。这有什么用呢?
你在开发过程中难免会产生一些 “只对你自己有用” 且 “不值得永久保留” 的东西。比如你在开发的过程中希望写几个简单的输入来测试你的程序,或者验证你程序里的某处是否写对了,于是你新建了个名字叫 test.txt
的文件,里面写了一些测试的内容,然后你在本地调试的时候会让你的程序读取这个文件。
test.txt
显然只是个用来存放写一些只对你自己有用的临时内容的文件,你不希望让 Git 每次都记录这个文件的更改 (因为没意义),所以你可以把它写进 .gitignore
中,来让 Git 忽略它。
其他类似的情况还包括,你使用 VS Code 或 IDEA 开发你的解释器,这些代码编辑器/IDE 可能会在项目中生成一些配置文件 (.vscode
或 .idea
),这些文件通常也是不需要被 Git 记录的,因为其中包含了你的一些个人配置。
推荐:
调试器(不做要求)
调试程序有几种方法:
- 硬看: 基本没啥用。
- print 大法: 对小问题有用,但一旦问题复杂起来,你将迷失在巨量的日志里,大脑过载,难以自拔。
- 使用 IDE 提供的调试功能: 比较有用,但也比较复杂。
- 使用 GDB/LLDB 等调试器: 非常有用,适用范围极广,但相对较难上手。
一些你需要知道的基本内容:
- 编译带调试信息的 C/C++ 程序:
gcc/g++ -g -O0 ...
。 - 用调试器载入程序:
gdb/lldb 程序名
。 - 用调试器载入程序并指定启动参数:
gdb --args 程序名 参数 ...
,lldb 程序名 -- 参数 ...
,此时会进入调试器的命令行。后续命令均需要在调试器的命令行中执行。 - 添加断点:
b 函数名
,b 文件名:行号
。 - 删除断点: GDB:
d 断点编号
,LLDB:br del 断点编号
。 - 查看所有断点: GDB:
info b
,LLDB:br list
。 - 执行程序:
r
。 - 单步执行,跳过函数:
n
。 - 单步执行,进入函数:
s
。 - 继续执行直到断点/出错:
c
。 - 查看调用栈:
bt
。 - 切换调用栈: GDB:
frame 编号
,LLDB:frame select 编号
。 - 暂停执行:
Ctrl + C
。 - 退出:
q
或Ctrl + D
。
一个例子:
你写的解释器出现了段错误——这种问题使用 print
大法调试效率很低,因为你很难知道你的解释器到底在何处出现了段错误,进而无法得知应该在何处插入 print
。遇到这种情况,不妨使用调试器载入程序并运行,当程序出现段错误时,调试器会停住并进入命令行供你操作。此时你就可以使用 bt
查看调用栈,定位出错的位置,然后在合适的地方下断点,并重新运行程序来进一步调试了。
推荐:
我们所有助教都强烈推荐大家抽一些时间,学习一下 MIT 的 The Missing Semester of Your CS Education,这会对你的日常开发起到相当大的帮助。