实现简易的C语言编译器(part 0)

0.1 引言

        工作之余,闲来无事,便根据多方搜集的资料,基于Python实现了一个简易的C语言编译器,可以称之为SCC(Simplified C Compiler)。整理了这段时间的学习过程,也分享出来,让更多愿意了解编译器的人少走一些弯路,提供更多可以参考的资料。

        相信如果开始学习这部分知识,可能大都是从《🐲书》这类经典书籍开始的。但是相信很多人屏住呼吸翻开一页又一页,又学到了多少知识,就因人而异了。反正,我没有看完那本书,反倒是这一系列文章(Let's Build A Simple Interpreter)浅显易懂地解释了Pascal语言解释器的实现方法,对我非常有启发和帮助。编译器并不是深不可测,只是从小坑里面好爬出来一些罢了。

        在进入下一个部分之前,让我们先想一想,为什么要学习编译器知识。

  • 没事干,像我一样,可以找点虐心的事情做。
  • 以后写程序遇到bug,就可以拓宽debug的范围了。
  • 成为写出(C++)++的那个人。
  • ......

        一切都得有一个目标,不然就没办法坚持下去。对于我自己而言,学习底层的知识,让自己能够系统性地思考,去面对各种上层调用带来问题,非常具有挑战性。

        好了,闲话少许,下面进入正题。

0.2 初识编译器

        这里简单介绍一下编译器的组成:


图1. 编译器组成

0.2.1 前处理

        这部分主要做三件事情:

  • 处理头文件
    #include "stdio.h"
    按照头文件引用顺序嵌套地将头文件的内容展开到当前文件中。如果嵌套引用到的文件很多,最终参与编译的源文件内容肯定超过了文件中原本的那些代码。只是大部分时候,我们将声明(.h文件)与实现(.c文件)分离,而.c文件可以单独生成目标文件(后缀名为.o),只需要在链接的时候添加上即可。因此并不需要全部展开到当前文件中。
  • 处理预编译指令
    C语言有很多的预编译指令。比如,非常常用的:
    #if XXX
    ...
    #elif XXX
    ...
    #else
    ...
    #endif
    
    实际上,现在的IDE工具已经能够直接进行辨识,直接就能告诉你用哪一块代码,剩下的就直接忽略了,不会进入编译过程。
  • 展开宏定义
    #define add(x, y) ((x) + (y))
    ((x) + (y))将代码中的add(x, y)全部替换,这也是为什么在学习C语言的过程中,不要吝惜用括号的缘故;同时,宏定义末尾也不能加分号等等。因此,当明白编译器怎么处理宏定义的时候,那么使用宏定义就能游刃有余了。

0.2.2 编译

        经过前处理过程处理的代码就开始进入编译过程。回顾一下,我们遇到的编译错误主要有哪些?以下面这段代码为例:

struct Point
{
    int x;
    int y;
}  // <- missing ';'  (2

struct Point pt = {1, 2};
int main()
{
    if (pt.x <> 2) // <- '<>' no such operator (1
        b = 2;  // <- 'b' is undefined (3
    return 0;  
}

        我在这里列举了三类错误,已经分别标注在上面对应的代码后面。那么,再设想一下,我们应该如何编写代码将这些错误找出来呢?
        很明显,第一种错误,也就是<>这种符号性质的错误,只需要从头到尾遍历一遍,就可以发现,根本不用做额外的工作。这就是我们将要介绍的词法分析
        对于第二种错误,如果不是结构体,而只是一般的函数块,也是不需要分号的。这时,我们必须要能够知道这里应该出现什么符号,不应该出现什么符号。这就需要对代码的结构有一定的认知,也就是语法分析
        那前面分析手段办不到的,自然就留给语义分析去做了:进行变量的声明检查。

0.2.2.1 词法分析

        词法分析是一个化整为零的过程。它从头到尾将源代码拆分成一个个的单元,称之为token。这些token按照空格、换行符和引号等进行拆分,可以是变量名、关键字、运算符号和其它字符。由于C语言并没有定义<>这样的二元比较操作符,此处就会产生错误提示信息。

0.2.2.2 语法分析

        语法分析则是一个化零为整的相反过程。它将token按照定义的语法要求组成表达式,语句和程序段。由于C语言要求结构体定义必须以;结尾,此处就会产生语法错误。这是很多人开始学C语言容易忘记的地方。
        一些时候,我们可能会遇到IDE提示一大堆错误,然后去出错的地方看,觉得也没有错误。其实这个时候,就是在最开始出错的地方前面,缺少;所致。不过,现在编译器功能越来越强大,很多时候能够直接准确定位错误。

0.2.2.3 语义分析

        词法分析只是将token组成了符合语法逻辑结构的片段,还需要语义分析进行上下文检查,即判断变量、函数是否已经定义或者类型是否匹配。显然,变量b开始使用的时候并没有定义,此处便是第三种语法错误。

0.2.2.4 汇编语言生成

        当然,经过了上面三个过程的仔细检查,我们可以放心地为源代码生成汇编语言代码了。目前,主流的汇编语言格式有Intel和AT&T两种,虽然格式还是有一定的差别,但是万变不离其中,本质上是相通的。
        这一步,也是最终影响程序运行性能的关键。我们将在后面详细讨论。

0.2.3 汇编

        汇编语言代码还需要经过汇编过程生成二进制代码,每条汇编指令都会生成一个相对于某个基地址的偏移地址。基地址大多数情况下都不是实际的物理地址。因此,并不能直接运行。

0.2.4 链接

        直到通过链接器对多个二进制代码的地址偏移重新编排,得到具有正确物理地址的二进制代码,这个时候,才能直接运行。

0.3 编译器命令行

        考虑hello.c文件下的代码:

#include "stdio.h"

int main(int argc, char* argv[])
{
    printf("hello world!");
    return 0;
}

接下来我们将使用成熟的C语言编译器对每一个过程进行命令行操作,从而与后面我们实际编写的代码生成的结果相比较。

  • 前处理过程
    clang -E hello.c -o hello.e
  • 语法分析和语义分析
    clang -fsyntax-only hello.c
  • 汇编语言生成
    clang -S hello.c -o hello.s
  • 汇编
    clang -o hello.o hello.s
  • 链接
    clang -o hello hello.o

更多的内容可以详见LLVM的官方文档。

        这样一看,编译器其实承担了非常繁杂的工作。在接下来的部分,这些内容都会一一呈现。

实现简易的C语言编译器(part 1)
实现简易的C语言编译器(part 2)
实现简易的C语言编译器(part 3)
实现简易的C语言编译器(part 4)
实现简易的C语言编译器(part 5)
实现简易的C语言编译器(part 6)
实现简易的C语言编译器(part 7)
实现简易的C语言编译器(part 8)
实现简易的C语言编译器(part 9)
实现简易的C语言编译器(part 10)
实现简易的C语言编译器(part 11)
实现简易的C语言编译器(part 12)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 第一章:编译和安装SCons第二章:简单编译第三章:编译相关的一些事情第四章:编译和链接库文件第五章:节点对象第六...
    仙灵儿阅读 11,719评论 0 3
  • TITLE: 编程语言乱炖 码农最大的烦恼——编程语言太多。不是我不学习,这世界变化快! 有时候还是蛮怀念十几、二...
    码园老农阅读 5,292评论 2 35
  • 一、以 Hello World开篇 Hello World对程序员而言肯定是如雷贯耳。但是简单的事物背后往往包含这...
    ZhengYaWei阅读 8,464评论 10 84
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,774评论 0 27
  • 链接地址:https://www.tutorialspoint.com/compiler_design/compi...
    dannyvi阅读 4,680评论 1 12