之前在写《编译系统 - 自底向上研究方法》的时候,本来想用 gcc 的编译器,汇编器,链接器 来做讲解。但 gcc 的代码量太庞大了。不太适合用来制作入门的编译系统课程。
当时我在想,有没一种编程语言,语法比 C 语言简单。编译器的实现不超过 2万行代码。当时没有找到,所以《编译系统》这本书的写作进度就停了。
直到昨天,我深夜没睡着,起来翻自己的书架。翻到了几年前买的一本书《计算机程序的构造与解析》,也就是 SCIP 。
SCIP 这本书我买来了大概 6 年了,我之前一直没法把它看完。
记得我工作了2年后买的这本书,我看到这本书是用 lisp 讲的,所以我就去看了 lisp 一些语法。但我是一个野生程序员,我当时是没有什么计算机基础的,平时的工作就是写写业务逻辑。所以当时我学 lisp ,除了学会敲括号,一些基本的语法,什么也没学到。当时我不会汇编,也不会C语言。
直到昨天,我再次翻开 SICP ,我突然明白要用什么样的方式来读这本书。我个人认为,学 SICP 的前提是掌握汇编,C语言,跟计算机原理。
为什么需要掌握汇编,因为当今的 Scheme 编译器已经能把 Scheme 翻译成 x86 指令集,你想了解这个编译过程,就需要学汇编。
为什么需要掌握C语言,因为 ChezScheme 的编译器就是 C语言写的,还有 GC 垃圾回收实现,这些都是 C 语言。
计算机原理,我这里指的是 进程,线程,互斥,虚拟内存,超线程 之类的基础知识。
所以我决定用 ChezScheme 的源码来写 《编译系统》那本书,因为 Scheme 的语法简单,虽然 Scheme 也可以扩展组合出复杂的语法,但是他的原始语法是原子性的,扩展后的复杂语法也是由原子组成,编译器只需要编译原子语法即可。所以 Scheme 的编译器实现代码会很少。
我为什么喜欢结合源码来分析,来写书?
我比较喜欢张银奎老师 的 ”格物致知“ 的思想,朱熹曾说:”言理则无可捉摸,物有时而离,言物则理自在,自是离不得。“
这句话的意思是,把理论跟实践分离开,空讲理论可能会让人摸不着头脑:相反,讲具体的事物,自然就包含了道理,二者是分不开的。
目前市面上很多书籍,都是讲概念理论,缺乏实践。我这里不是指这些讲概念的书是烂书,有些书讲概念确实很好,我也经常看,作者的水平也是经过验证的。
但是这些讲概念的书,不适合作为入门读物。因为向一个初学者传授知识,至少要给他提供源码,搭建调试环境,一点点调试。
举个例子,在我21岁的时候,我就买了 《算法导论》相关的算法书籍来看,当时我在看 B 树的算法实现,树的节点在 C 语言里都是用指针连接的嘛。我在网上找了好多个demo,都是 C 语言实现,但是这些demo都没有实现存储到硬盘。但是我当时有个疑惑,C语言有指针,但是硬盘里面没有指针这种结构,数据库的实现就用到 B 树。我不可能把一个指针 64 位的数据存到硬盘。这样没用。指针 怎么存储到硬盘?这个问题困扰了我一年多。
因为我不知道 指针 怎么存储到硬盘?B 树的结构在硬盘怎么实现,所以算法相关的书籍,我就不再看了,因为我不知道怎么把他应用到生产环境,生产环境有硬盘。
后来我开始读《unix环境高级编程》,也就是 APUE,当我看到最后一章,调 Linux 的API函数实现一个简单的数据库,好像是一个 hash 数据库。
APUE 提供的代码是可以正常跑的,我花了几天时间把那个数据库实现代码看完,当我看到他用 fseek
那个函数操作文件的时候,困扰我1年多的问题,终于有答案。
其实不是什么指针存储到硬盘。指针是内存条的 offset,控制从内存的哪个位置读写数据。而 硬盘的 offset 就是可以通过 fseek
函数进行操作。
fseek
函数可以控制从硬盘的哪个位置读写数据。位置就是指针,位置就是 offset,offset 就是 指针。
因此,我个人认为,只要有源码,能运行,能调试,计算机领域很多很复杂的东西,其实都可以变得很简单。跟初学者讲概念,堆砌逻辑,还不如给他源码,让他一点点调试,自己把逻辑弄懂。文字描述是有歧义,每个人的理解可能不一样。但是代码调试没有歧义,是什么就是什么。
现在市面上一些书籍没有结合源码调试讲解,我个人觉得是因为,计算机里面的很多软件已经发展得很庞大,例如浏览器的代码几百万行,mysql 的代码几十万行。
不像早期,操作系统的代码很少,可能只有几千行。所以很容易结合源码讲解。
所以本文主要讲解如何 搭建 ChezScheme 的调试环境,如何用 clion 来断点调试 ChezScheme 的 C语言写的 Scheme 编译器。
ChezScheme 在 2016 年开源了代码,本文使用的版本是 9.5.6 。
先来讲解一些 ChezScheme 在 Windows10 系统的编译以及安装。
ChezScheme 下载之后目录如下:
要先安装 msys2 ,然后进入 msys2 命令行,如下:
提示:ChezScheme 的代码文件格式是 dos 的,所以最好用 msys2 编译,如果用 原生的 Linux 系统,会有问题。
cd \msys64
.\msys2_shell.cmd -mingw64
然后进入 ChezScheme-9.5.6 的 wininstall 目录,如下:
然后执行以下命令编译
make workareas
好像不需要执行 make install
上面的命令跑完之后,就会在项目根目录生成 4 个文件夹,a6nt , i3nt,ta6nt,ti3nt
a6 代表 AMD x64,早期 64位技术,intel 跟 AMD 都有各自的名字,现在已经统一,把他理解成 x64 就行。
nt 代表 Windows 的 NT 内核,3代表Intel 386,即32位。
前面的 t 代表是否启动多线程 thread。
现在 直接进入 a6nt 目录,ChezScheme 的编译脚本会把根目录很多文件复制到 a6nt 来进行编译,会发现 a6nt 目录有个 c 文件夹,就是从 根目录复制过去的。
可以看到,有个执行文件 scheme.exe。
新建一个 t.ss 文件,内容如下:
;;; doubly recursive power (expt) function
;;; try using trace-lambda to see the nesting.
(define power
(lambda (x n)
(cond
[(= n 0) 1]
[(= n 1) x]
[else
(let ([q (quotient n 2)])
(* (power x q) (power x (- n q))))])))
(define y (power 5 2))
(pretty-print y)
用法如下:
cd C:\msys64\home\loken\ChezScheme-9.5.6\a6nt
.\bin\a6nt\scheme.exe --script t.ss
scheme 语法 看这本书 https://www.scheme.com/tspl4/
上面的流程,好像 scheme.exe 是一个解析器,没有把 ss 编译成 exe 来执行。但实际上里面是编译出字节码执行的了, scheme.exe 好像没办法编译出 exe,因为要根据不同平台做很多链接器的工作,所以他好像是直接把编译后的字节码插入到当前线程空间来执行,这样省事。
因此,现在我们只需要 知道 scheme.exe 这个文件是怎么编译出来的,移植到 clion ,即可断点调试他的编译器实现。
也可以用 WinDbg 来断点,我试一下,也能跳到源码,如下:
然后直接在 用以下命令设置个断点:
bu scheme!wmain
现在就可以断点调试他的编译器实现了。其实 WinDbg 也挺好用,
也可以参考之前的文章《用VsDebug断点调试FFmpeg》,用 VsDebug 来调试。
我发现 WinDbg 或者 VsDebug 已经够用了,移植到 clion 有点没事找事,所以还是不管 clion 了。
vs2019 的调试界面如,VsDebug 真好用:
ChezScheme 编译脚本分析记录。
.PHONY: workareas
workareas:
cd ..; ./configure -m=a6nt; $(MAKE) -C a6nt
cd ..; ./configure -m=i3nt; $(MAKE) -C i3nt
cd ..; ./configure -m=ta6nt; $(MAKE) -C ta6nt
cd ..; ./configure -m=ti3nt; $(MAKE) -C ti3nt
workarea 这个脚本 代码 就是不断地复制文件。
workdir $W/s
(cd $W/s; workln ../../s/Mf-$M Mf-$M)
(cd $W/s; forceworkln Mf-$M Makefile)
(cd $W/s; workln ../../s/Mf-base Mf-base)
(cd $W/s; workln ../../s/Mf-cross Mf-cross)
(cd $W/s; workln ../../s/$M.def $M.def)
(cd $W/s; forceworkln2 $M.def machine.def)
workdir $W/mats
(cd $W/mats; workln ../../mats/Mf-$M Mf-$M)
(cd $W/mats; forceworkln Mf-$M Makefile)
(cd $W/mats; workln ../../mats/Mf-base Mf-base)
(cd $W/mats; workln ../../mats/Mf-exobj Mf-exobj)
case $M in
*nt)
(cd $W/mats; workln ../../mats/vs.bat vs.bat)
;;
esac
for dir in `echo examples unicode` ; do
workdir $W/$dir
for file in `(cd $dir ; echo *)` ; do
(cd $W/$dir ; workln ../../$dir/$file $file)
done
done
请问您的书是否已经出版了呢
@hello 还没有出版