来源:伯乐在线,作者:Chaobs

首先向C语言之父Dennis Ritchie致敬!

当今几乎所有的实用的编译器/解释器(以下统称编译器)都是用C语言编写的,有一些语言比如Clojure,Jython等是基于JVM或者说是用Java实的,IronPython等是基于.NET实现的,但是Java和C#等本身也要依靠C/C++来实现,等于是间接调用了C。所以衡量某种高级语言的可移植性其实就是在讨论ANSI/ISO C的移植性

C语言是很低级的语言,很多方面都近似于汇编语言,在《Intel 32位汇编语言程序设计》一书中,甚至介绍了手工把简单的C语言翻译成汇编的方法。对于编译器这种系统软件,用C语言来编写是很自然不过的,即使是像Python这样的高级语言依然在底层依赖于C语言(举Python的例子是因为Intel的黑客正在尝试让

Python不需要操作系统就能运行——实际上是免去了BIOS上的一次性C代码)。现在的学生,学过编译原理后,只要有点编程能力的都可以实现一个功能简单的类C语言编译器。

可是问题来了,不知道你有没有想过,大家都用C语言或基于C语言的语言来写编译器,那么世界上第一个C语言编译器又是怎么编写的呢?这不是一个“鸡和蛋”的问题…

还是让我们回顾一下C语言历史:1970年Tomphson和Ritchie在BCPL(一种解释型语言)的基础上开发了B语言,1973年又在B语言的基础上成功开发出了现在的C语言。

在C语言被用作系统编程语言之前,Tomphson也用过B语言编写过操作系统。可见在C语言实现以前,B语言已经可以投入实用了。因此第一个C语言编译器的原型完全可能是用B语言或者混合B语言与PDP汇编语言编写的。

我们现在都知道,B语言的执行效率比较低,但是如果全部用汇编语言来编写,不仅开发周期长、维护难度大,更可怕的是失去了高级程序设计语言必需的移植性。上一张图大家感受一下这巨大的差别!!!

C语言
CN语言
……
C0语言
汇编语言
机器语言

那么这种大胆的子集简化的方法,是怎么实现的,又有什么理论依据呢?

先介绍一个概念,“自编译”Self-Compile,也就是对于某些具有明显自举性质的强类型(所谓强类型就是程序中的每个变量必须声明类型后才能使用,比如C语言,相反有些脚本语言则根本没有类型这一说法)编程语言,可以借助它们的一个有限小子集,通过有限次数的递推来实现对它们自身的表述,这样的语言有C、Pascal、Ada等等,至于为什么可以自编译,可以参见清华大学出版社的《编译原理》,书中实现了一个Pascal的子集的编译器。

总之,已经有计算机科学家证明了,C语言理论上是可以通过上面说的CVM的方法实现完整的编译器的,那么实际上是怎样做到简化的呢?

这张图是不是有点熟悉?对了就是在讲虚拟机的时候见到过,不过这里是CVM(C Language Virtual Machine),每种语言都是在每个虚拟层上可以独立实现编译的,并且除了C语言外,每一层的输出都将作为下一层的输入(最后一层的输出就是应用程序了),这和滚雪球是一个道理。用手(汇编语言)把一小把雪结合在一起,一点点地滚下去就形成了一个大雪球,这大概就是所谓的0生1,1生C,C生万物吧?

下面是C99的关键字:

  1. auto enum restrict unsigned

  2. break extern return void

  3. case float short volatile

  4. char for signed while

  5. const goto sizeof _Bool

  6. continue if static _Complex

  7. default inline struct _Imaginary

  8. do int switch

  9. double long typedef

  10. else register union

  11. //共37个

仔细看看,其实其中有很多关键字是为了帮助编译器进行优化的,还有一些是用来限定变量、函数的作用域、链接性或者生存周期(函数没有)的,这些在编译器实现的早期根本不必加上,于是可以去掉auto, restrict, extern, volatile, const, sizeof, static, inline, register, typedef,这样就形成了C的子集,C3语言,C3语言的关键字如下:

  1. enum unsigned

  2. break return void

  3. case float short

  4. char for signed while

  5. goto _Bool

  6. continue if _Complex

  7. default struct _Imaginary

  8. do int switch

  9. double long

  10. else union

  11. //共27个

再想一想,发现C3中其实有很多类型和类型修饰符是没有必要一次性都加上去的,比如三种整型,只要实现int就行了,因此进一步去掉这些关键词,它们是:unsigned, float, short, char(char 是 int), signed, _Bool, _Complex, _Imaginary, long,这样就形成了我们的C2语言,C2语言关键字如下:

  1. enum

  2. break return void

  3. case

  4. for while

  5. goto

  6. continue if

  7. default struct

  8. do int switch

  9. double

  10. else union

  11. //共18个

继续思考,即使是只有18个关键字的C2语言,依然有很多高级的地方,比如基于基本数据类型的复合数据结构,另外我们的关键字表中是没有写运算符的,在C语言中的复合赋值运算符->、运算符的++、– 等过于灵活的表达方式此时也可以完全删除掉,因此可以去掉的关键字有:enum, struct, union,这样我们可以得到C1语言的关键字:

  1. break return void

  2. case

  3. for while

  4. goto

  5. continue if

  6. default

  7. do int switch

  8. double

  9. else

  10. //共15个

接近完美了,不过最后一步手笔自然要大一点。这个时候数组和指针也要去掉了,另外C1语言其实仍然有很大的冗杂度,比如控制循环和分支的都有多种表述方法,其实都可简化成一种,具体的来说,循环语句有while循环,do…while循环和for循环,只需要保留while循环就够了;分支语句又有if…{}, if…{}…else, if…{}…else if…, switch,这四种形式,它们都可以通过两个以上的if…{}来实现,因此只需要保留if,…{}就够了。可是再一想,所谓的分支和循环不过是条件跳转语句罢了,函数调用语句也不过是一个压栈和跳转语句罢了,因此只需要goto(未限制的goto)。因此大胆去掉所有结构化关键字,连函数也没有,得到的C0语言关键字如下:

  1. break void

  2. goto

  3. int

  4. double

  5. //共5个

这已经是简约的极致了。

只有5个关键字,已经完全可以用汇编语言快速的实现了。通过逆向分析我们还原了第一个C语言编译器的编写过程,也感受到了前辈科学家们的智慧和勤劳!我们都不过是巨人肩膀上的灰尘罢了!0生1,1生C,C生万物,实在巧妙!

深度 || 既然C编译器是C语言写的,那么第一个C编译器是怎样来的?相关推荐

  1. Linux下vi编译器用C语言写九九乘法表

    1.创建一个文件夹,如 : 2.进入vi命令行模式,然后再按i.o.a进入编辑模式. 3.代码如下: 4.保存并退出. 5.用gcc编译器进行编译链接.multable.c是源文件,-o是生成目标文件 ...

  2. c语言编译器_学C语言写自己的K语言:编译器词法分析。

    词法分析(lexical analysis),是计算机科学中将字符序列转换为记录(Token)序列化的过程.词法分析一般分手动与自动,自动是基于lex,flex词法分析器使用正则式来配置,我们这里将学 ...

  3. c语言写os 编译器,你真的懂''Hello world''吗?从编译器到OS内核系列:编译器基本概念...

    本文是<你真的理解'Hello world'吗?从编译链接到OS内核系列专题>的第一章的第一小节,主要介绍编译器的基本概念以及C语言程序的构建过程.全系列大纲如下: 前言 第1章 编译器的 ...

  4. 【C语言进阶深度学习记录】五 C语言中变量的属性

    上一篇文章学习了C语言中的类型转换,点击链接查看:[C语言进阶深度学习记录]四 C语言中的类型转换. 文章目录 1 C语言的变量属性 1.1 auto关键字 1.2 register关键字 1.3 s ...

  5. C语言这么厉害,它自身又是用什么语言写的?

    这是来自我的星球的一个提问:"C语言本身用什么语言写的?" 换个角度来问,其实是:C语言在运行之前,得编译才行,那C语言的编译器从哪里来? 用什么语言来写的?如果是用C语言本身来写 ...

  6. 让C语言写图形库时不关闭控制台,GCC编译Win图形程序不显示控制台方法

    用VS编译openCV这些有控制台又有图形显示的程序,如果想隐藏控制台,只需要使用一行代码: #pragma comment( linker, "/subsystem:/"wind ...

  7. 嵌入式C语言自我修养:从芯片、编译器到操作系统(附送书籍)

    关注+星标公众号,不错过精彩内容 来源 | 宅学部落 最近,阅读了王工(王利涛)赠送的一本由他编著的书籍<嵌入式C语言自我修养>,感觉写的挺不错.今天分享一下这本书籍<嵌入式C语言自 ...

  8. 每日一书丨嵌入式C语言自我修养:从芯片、编译器到操作系统

    最近,阅读了王工(王利涛)赠送的一本由他编著的书籍<嵌入式C语言自我修养>,感觉写的挺不错.今天分享一下这本书籍<嵌入式C语言自我修养>:从芯片.编译器到操作系统. 从芯片.编 ...

  9. 把C++当脚本语言写

    把C++当脚本语言写! 提到脚本,脑海里马上闪过一大堆:Python,Perl,Ruby,PHP,JS,VBS,LUA... 不过你有没听说过,用经典的C++做脚本语言吗?先不多说,上个图.(先别纠结 ...

最新文章

  1. php cc攻击代码,php cc攻击代码与防范方法
  2. ORA-00910: specified length too long for its datatype
  3. IBM软件OEM概览
  4. Bootstrap 图片样式
  5. bzoj3202:[Sdoi2013]项链
  6. mean shift
  7. c51用c语言写PO口复用6,毕业回馈—89C51之GPIO使用(示例代码)
  8. 绿联USB转RS-485/422转换器
  9. 五笔字根表识别码图_识别码五笔字根表打印
  10. 图片格式 tif转jpg 用Matlab实现
  11. Spire.Office for Java 7.10.FIX[7.9.9]-全新版
  12. vs 设置起始页不见了_VS2015,为什么模板不见了,请教
  13. HDU-5514 Frogs (容斥)
  14. Android高性能音频之opensl es播放流程(七)
  15. Online Tools
  16. 多智能体强化学习综述-Lucian Busoniu
  17. 多么痛的领悟~ T T
  18. 不用删除密码卸载Symantec
  19. 案例: 场景:比如 在项目中显示所有项目/某用户的所有项目Controller中 ulr对应的名字是一致
  20. Face Recognition Algorithms

热门文章

  1. Spring Mvc使用Jackson进行json转对象时,遇到的字符串转日期的异常处理(Can not deserialize value of type Date from String)
  2. vue baidu map之获取选中点的经纬度
  3. 项目中的textarea遇到的小问题
  4. Jquery 小技巧
  5. linux裁剪内核和移植,嵌入式Linux内核裁剪及移植的研究与实现
  6. linux链接时报未定义的引用,g ++链接或引用不与本地安装的库一起使用:未定义的引用...
  7. linux查看系统内存和使用量,LINUX 查看当前系统的内存使用情况
  8. centos7赋予全部权限_终结CentOS 7+Snort2.9+BASE 安装
  9. wegame饥荒一直连接中_谁是老牛?谁是嫩草?WeGame与老牌网游的故事 | 游戏茶馆...
  10. java如何使用promql_Prometheus 常用 PromQL 语句