前置知识

本文改编自 MaxXing 编写的《编译原理》实践文档相关章节。

本节介绍了完成《软件设计实践》大作业所需的部分前置知识。如果你对此并不了解,建议你先花一些时间进行学习。

C++ 面向对象风格编程

你将在本项目中充分运用前半学期所学到的面向对象编程知识。如果你对这方面的知识并不熟练,你可以时刻复习相关的课件。

Lisp

你需要了解 Lisp 这种编程语言。如果你还没有了解的话,可以读一下 30 分钟 Lisp 入门教程open in new window

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
  • 退出: qCtrl + D

一个例子:

你写的解释器出现了段错误——这种问题使用 print 大法调试效率很低,因为你很难知道你的解释器到底在何处出现了段错误,进而无法得知应该在何处插入 print。遇到这种情况,不妨使用调试器载入程序并运行,当程序出现段错误时,调试器会停住并进入命令行供你操作。此时你就可以使用 bt 查看调用栈,定位出错的位置,然后在合适的地方下断点,并重新运行程序来进一步调试了。

推荐:


我们所有助教都强烈推荐大家抽一些时间,学习一下 MIT 的 The Missing Semester of Your CS Educationopen in new window,这会对你的日常开发起到相当大的帮助。

Last Updated:
Contributors: Guyutongxue