学习较底层编程:动手写一个C语言编译器

动手编写一个编译器,学习一下较为底层的编程方式,是一种学习计算机到底是如何工作的非常有效方法。

编译器通常被看作是十分复杂的工程。事实上,编写一个产品级的编译器也确实是一个庞大的任务。但是写一个小巧可用的编译器却不是这么困难。

秘诀就是首先去找到一个最小的可用工程,然后把你想要的特性添加进去。这个方法也是Abdulaziz Ghuloum在他那篇著名的论文“一种构造编译器的捷径”里所提到的办法。不过这个办法确实可行。你只需要按照这篇论文中的第一步来操作,就可以得到一个真正可用的编译器!当然,它只能编译程序语言中的非常小的子集,但是它确实是一个真实可用的编译器。你可以随意的扩展这个编译器,然后从中学到更多更深的知识。

受到这篇文章的鼓舞,我就写了一个C编译器。从某种意义上来说这比写一个scheme的编译器要困难一些(因为你必须去解析C那复杂的语法),但是在某些方面又很便利(你不需要去处理运行时类型)。要写这样一个编译器,你只需要从你那个可用的最小的编译器开始。

对于我写的编译器来说,我把它叫 babyc,我选了这段代码来作为我需要运行的第一个程序:

1
2
3
int main() {
    return 2;
}

没有变量,没有函数调用,没有额外的依赖,甚至连if语句,循环语句都没有,一切看起来是那么简单。

我们首先需要解析这段代码。我们将使用 Flex 和 Bison 来做到这点。这里有怎么用的例子可以参考,幸好我们的语法是如此简单,下面就是词法分析器:

1
2
3
4
5
6
7
8
9
"{" { return '{'; }
"}" { return '}'; }
"(" { return '('; }
")" { return ')'; }
";" { return ';'; }
[0-9]+ { return NUMBER; }
"return" { return RETURN; }
"int" { return TYPE; }
"main" { return IDENTIFIER; }

这里是语法分析器:

1
2
3
4
5
6
7
function:
    TYPE IDENTIFIER '(' ')' '{' expression '}'
    ;
expression:
    RETURN NUMBER ';'
    ;

最终,我们需要生成一些汇编代码。我们将使用32位的X86汇编,因为它非常的通用而且可以很容易的运行在你的机器上。这里有X86汇编的相关网站。

下面就是我们需要生成的汇编代码:

1
2
3
4
5
6
7
.text
        .global _start # Tell the loader we want to start at _start.
_start:
        movl    $2,%ebx # The argument to our system call.
        movl    $1,%eax # The system call number of sys_exit is 1.
        int     $0x80 # Send an interrupt

然后加上上面的词法语法分析代码,把这段汇编代码写进一个文件里。恭喜你!你已经是一个编译器的编写者了!

Babyc 就是这样诞生的,你可以在这里看到它最开始的样子。

当然,如果汇编代码没办法运行也是枉然。让我们来用编译器生成我们所希望的真正的汇编代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Here's the file we want to compile.
$ cat return_two.c
#include <stdio.h>
int main() {
    return 2;
}
# Run the compiler with this file.
$ ./babyc return_two.c
Written out.s.
# Check the output looks sensible.
$ cat out.s
.text
    .global _start
_start:
    movl    $2, %ebx
    movl    $1, %eax
    int     $0x80

非常棒!接着让我们来真正的运行一下编译之后代码来确保它能得到我们所想的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
# Assemble the file. We explicitly assemble as 32-bit
# to avoid confusion on x86_64 machines.
$ as out.s -o out.o --32
# Link the file, again specifying 32-bit.
$ ld -m elf_i386 -s -o out out.o
# Run it!
$ ./out
# What was the return code?
$ echo $?
2 # Woohoo!

我们踏出了第一步,接下去怎么做就全看你了。你可以按照那篇文章所指导的全部做一遍,然后制作一个更加复杂的编译器。你需要去写一个更加精巧的语法树来生成汇编代码。接下去的几步分别是:(1)允许返回任意的值(比如,return 3; 一些可执行代码);(2)添加对“非”的支持(比如,return ~1; 一些可执行代码)。每一个额外的特性都可以教你关于C语言的更多知识,编译器到底是怎么执行的,以及世界上其他编写编译器的人是如何想的。

这是构建 babyc 的方法。Babyc 现在已经拥有了if语句,循环,变量以及最基础的数据结构。欢迎你来check out它的代码,但是我希望看完我的文章你能够自己动手写一个。

不要害怕底层的一些事情。这是一个非常奇妙的世界。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/447322.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Arrays.deepToString() 方法同时适用于基元数组和对象数组

Arrays.deepToString() 方法同时适用于基元数组和对象数组&#xff1a; import java.util.*;public class MultiDimWrapperArray {public static void main(String[] args) {Integer[][] a1 { // Autoboxing{ 1, 2, 3, },{ 4, 5, 6, },};Double[][][] a2 { // Autoboxing{ {…

高效程序员的7个共同特征

要想成为高效的程序员&#xff0c;你需要具备一定的综合素质才能够让你用你所掌握的技能、经验和知识编写出有效的代码。有一些开发人员在技术方面具备一定的技巧&#xff0c;但他们永远无法成为高效的程序员&#xff0c;就是因为他们缺乏所需的其它几项特质。本文将给出成为一…

java.util.Array中的方法

概述 asList(): 获取任何序列或数组&#xff0c;并将其转换为一个 列表集合 &#xff08;集合章节介绍了此方法&#xff09;。 copyOf()&#xff1a;以新的长度创建现有数组的新副本。 copyOfRange()&#xff1a;创建现有数组的一部分的新副本。 equals()&#xff1a;比较两…

有关编程的12个猜想

摘要&#xff1a;编程世界的将来如何目前仍难预料&#xff0c;但可以肯定的一点是技术一直在加速发展。本文搜罗出12个独特的编程视角猜想&#xff0c;一起来看看有哪些猜想在不久的将来就能变为现实。 编程世界的将来如何目前仍难预料&#xff0c;但可以肯定的一点是技术一直…

面试中如何剔除“鱼目混珠”程序员?

公司招聘面试事宜是一个耗时耗钱的项目&#xff0c;从挑选简历开始&#xff0c;还要花更多的时间面试候选人。有的时候这些人才机构会向你保证这些人都是Java天才、SQL专家、堆栈开发者等等&#xff0c;但实际上真实情况远不及你想想的。对于一个公司来说&#xff0c;执行招聘面…

InputStream 类型

输入流类型 I/O-1 类功能构造器参数如何使用ByteArrayInputStream允许将内存的缓冲区当做 InputStream 使用缓冲区&#xff0c;字节将从中取出作为一种数据源&#xff1a;将其与 FilterInputStream 对象相连以提供有用接口StringBufferInputStream将 String 转换成 InputStr…

java容器相关问题

同步类容器 1&#xff0c;这些复合操作在多线程并发地修改容器时&#xff0c;可能会表现出意外的行为&#xff0c;最经典的便是ConcurrentModificationException&#xff0c;原因是当容器迭代的过程中&#xff0c;被并发的修改了内容&#xff0c;这是由于早期迭代器设计的时候…

趣文:如果编程语言是车

C语言是全能手&#xff0c;小巧&#xff0c;强大&#xff0c;所向披靡&#xff0c;可靠&#xff0c;任何事情都能对付。 C是新的C&#xff0c;双倍的能力&#xff0c;双倍的尺寸&#xff0c;适应险恶的环境&#xff0c;但是你如果没练好就去驾驶&#xff0c;很可能会撞车。 C#是…

Java 线程安全

线程安全 线程安全概念&#xff1a;当多个线程访问某一个类&#xff08;对象或方法&#xff09;时&#xff0c;这个类始终都能表现出正确的行为&#xff0c;那么这个类&#xff08;对象或方法&#xff09;就是线程安全的。synchronized&#xff1a;可以在任意对象及方法上加锁…

开发者应该了解的API技术清单!

摘要&#xff1a;有人说&#xff0c;有API的地方就有App&#xff0c;借助这些API开发者轻松构建出一款应用&#xff0c;极大地提高开发效率和开发质量。文中整理了一份API服务清单&#xff0c;内容涵盖&#xff1a;监控/调试、 CDN 、数据库、仪表盘、支付、通信等方面&#xf…

提高程序员职场价值的10大技巧

如果你已经是个很牛叉的程序员&#xff0c;但是依然觉得觉得还不够的话&#xff0c;欢迎阅读此文。本文旨在帮助各位更上一层楼。 你是不是觉得自己已经掌握了所有的编程技巧&#xff1f;别太自以为是了&#xff01; 会写代码的确很重要&#xff0c;但是要拿到更好薪水&#…

google python的风格规范

点击链接&#xff0c;查看内容

IT人应当知道的10个行业小内幕

如果你打算从事IT行业或刚进入这个行业&#xff0c;也许本文下面的小内幕会吓到你&#xff0c;因为这些事平常都不会公开讨论的。如果你是IT资深人士&#xff0c;或许你已经遇到其中的大部分了。如果你愿意&#xff0c;请一起来参与讨论吧。 这些内幕大多数是针对网络管理员、…

Volatile原子性一致性JVM指令重排

概念 Volatile概念&#xff1a;Volatile关键字的主要作用是使变量在多个线程间可见。作用&#xff1a; 在多线程间可以进行变量的变更&#xff0c;使得线程间进行数据的共享可见 阻止指令重排序&#xff0c;happens-before package com.example.core.cas;import com.example.c…

python修改文件内容,不需要read,write多个动作。

python 要修改文件内容&#xff0c;常用 是先read&#xff0c;后write &#xff0c; 再 rename&#xff0c;很不爽。 比如&#xff1a;需要 把 yuv_dir "../HD/" # "H:/HD_Master/1080i25/" 改为 yuv_dir "C:/HD/" # "H:…

Atomic系列类

Atomic系列类别 Atomic系列类封装了一系列的基础类型和对象操作&#xff0c;其主要目的就是为了实现原子性&#xff0c;主要核心类如下 AtomicIntegerAtomicLongAtomicBooleanAtomicIntegerArrayAtomicLongArrayAtomicReference 原子性的引用对象在对Atomic类操作的时候&…

python 系统学习笔记(十二)---os os.path os.walk

得到当前工作目录&#xff0c;即当前 Python脚本工作的目录路径: os.getcwd() 返回指定目录下的所有文件和目录名:os.listdir()函数用来删除一个文件:os.remove()删除多个目录&#xff1a;os.removedirs&#xff08;r“c&#xff1a;\python”&#xff09;检验给出的路径是否是…

Java JUC工具类--CountDownLatch

CountDownLatch&#xff1a;用于监听某些初始化操作&#xff0c;并且线程进行阻塞&#xff0c;等初始化执行完毕后&#xff0c;通知主线程继续工作执行 package com.example.core.juc;import java.util.concurrent.CountDownLatch;public class UseCountDownLatch {public stat…

Java JUC工具类--CyclicBarrier

CyclicBarrier&#xff1a;栅栏的概念&#xff0c;多线程的进行阻塞&#xff0c;等待某一个临界值条件满足后&#xff0c;同时执行 类比&#xff1a;每个线程代表一个跑步运动员&#xff0c;当运动员都准备好后&#xff0c;才一起出发&#xff0c;只要有一个人没有准备好&#…