C语言编译器概要设计思路一

简介: C语言编译器概要设计思路一

编译器


1、编译器定义

将高级别语言翻译成更底层的机器可执行的语言

2、工业级编译器的编译过程

编译过程分前端和后端两个阶段

2-1 前端

前端即parser:将源代码翻译成中间代码,以便给后端程序进一步处理

parser过程分两个步骤

  • 词法分析即tokenize

词法分析的目标是把人类语言简单处理一下告诉计算机这些词都是什么含义

比如把int单词识别出来告诉计算机是整型;add识别出来告诉计算机是一个函数

  • 语法分析

通过词法分析计算机已经知道每个词是什么意思 通过语法分析可以将词语组成一个完整的句子

语法分析的目标就是按照语法(蕴含着逻辑和运算)生成中间代码,这个中间代码就是AST(抽象语法树)

image.png

用逆波兰式来表示:

+(+(*32)4)6

有些语言比如lisp这是函数式语言,本身的语言的原始状态就非常类似于逆波兰式,这种语言会很轻易的得到抽象语法树

高级语言比如java、c想要得到最终语法树想要经历上述2个过程

2-2后端

后端是编译最核心的关节

分为2个组件

  • 可选的optimizer优化器(编译器最难最核心的组件)

输入中间代码经历一系列的优化过程又得到了同一种的中间代码

它做了一些优化工作比如剔除不影响结果的没用的代码(死代码剔除)、转换一些逻辑(函数内联)、做一些替换(常量替换)得到一个更利于执行的性能更高的代码

  • Code Generator即CodeGen代码生成器

生成最终目标平台(目标机器)的可执行代码的生成器

将中间代码翻译成目标代码

比如翻译成x86平台的汇编:movq、push、add

java虚拟记jvm:load、store、add

llvm(LR)语言给各个平台 比如x86平台、amd平台、arm架构等都实现了上述两个过程

想要做一个可用的编译器的话 最简单的方式就是写个parser把目标的语言翻译成llvm(LR) 选定想要的平台生成最终的机器代码

这个语言不仅用在编译器领域,也用在机器学习、数据库优化等领域


自定义C语言编译器设计思路


自定义c语言编译器的设计思路

  • 前后端合一,没有中间优化过程
  • 目标代码基于自定义的VM即不是x86也不是jvm

目标是为了简单,这个自定义的虚拟机麻雀虽小但五脏俱全

  • 编译过程是one pass的过程即读到源码,一行一行的去编译和分析

当把所有代码全部pass完了之后,目标代码就已经生成好了,就不需要再读源码了

源码只读一遍就可以完成parse和codeGen的过程得到想要的自定义虚拟机的机器码

但是有一些c语言的特性是不支持的 但也无伤大雅

比如说函数的局部变量必须得一定在函数的最开头的位置

int add(int a,int b){
   int ret;
   int tmp;
   tmp=a+b;
   ret=tmp;
   return ret;
}
ret和tmp这两个局部变量
其实也可以定义在a+b后面
但因为是one-pass过程
所以必须定义在add函数最开始的位置


自制VM设计概要设计


1 计算是基于集群器Register和stack两个组件来做的

这样做的目的是为了简化

比如有个两元计算

加法操作:两个数相加a+b

如果用传统的x86的物理机的架构

它的运算单元在cpu内部

能够处理的数据在cpu内部的寄存器 比如ax、bx

这些寄存器能够做运算把结果输出到寄存器

不能基于栈做运算

因为栈在内存里

内存的读取速度跟计算cpu的速度相差100倍以上

所以在实际的物理机中要计算a+b需要把数据从内存加载到寄存器

运算完之后再写回寄存器

如果要存储这个资源 还需要再写回内存

这里为了简化设计 只有一个通用的寄存器

一元计算直接基于这个寄存器

二元计算基于栈顶(stack peek)去跟通用寄存器一起去计算

所以它的ALU(算数逻辑单元)是基于stack和ax这两个位置运行的

2 pc寄存器 program counter

指定目前运行到哪条指令了

下条指令就是pc+1

保持代码按既有的顺序去执行

有2个指针寄存器

一个是stack pointer(sp 维护栈顶)一个是base pointer(bp 维护上一个栈的栈顶:如果这个栈要回去的时候能找到上一个栈在什么地方 一个函数调用起一个新栈 执行完函数要回到函数之前的位置 代码是可以回去了 栈的内容也得回到原来的位置)

3 内存空间

  • vm指令编译好之后 得有一个内存空间来存放
  • 存放栈的空间
  • 还有静态数据、字面量在编译的过程中存在data的内存空间里

比如32位地址空间的程序有4G的虚拟内存会分为这几块

比如 4+3*2+6 做成的AST:

image.png

最顶上是内核代码 做系统调用的话 会读到内核的代码

在这里设计VM的时候没有用到堆

因为并不想主动去实现动态内存

动态内存可以通过一种作弊的方式去实现即Native-Call

4 指令集

包括3块

4-1一个是save and load这一类的指令

内存到寄存器的操作叫做load

寄存器写回内存叫save

这一类的内存和寄存器的指令都叫save and load

4-2第二个就是运算类的指令

  • 算术运算:四则运算
  • 位运算
  • 逻辑运算:与 或 非

4-3第三个就是分支跳转类指令

语言实现判断、循环、函数跳转

4-4为了简化实现 做了一些作弊类的指令Native-Call

它主要处理IO(print、open、write、read文件)和动态内存(malloc、free、memset)的操作

正常的平台是不需要Native-Call的 通过前3类指令可以实现

但比较复杂 这里为了简化实现 做了作弊类的指令


大致了解VM的运行原理


image.png

有一段代码 helloworld

编译完之后 代码区存放指令

data存放字面量、helloworld的ascii码

栈是从大到小的 栈刚编译完的时候 sp、bp都指向初始位置max位置

data和code是从小到大的 初始位置是处于0的位置

ax寄存器是一个空的状态

pc寄存器开始在0的位置 执行第一行代码pc+1

把指令读出来

看看是什么指令 做相应的操作

看是去data区取数据呢

还是栈的指针向下减呢

还是把栈里的数据加载到寄存器呢

还是对寄存器和栈里的数据做一个计算呢

根据指令的不同就会有不同的行为结果

当把所有的指令执行完 退出

当然也会有跳转 pc可能跳转到其他位置了

最终会把所有的指令(代码)执行完

栈指针也会通过函数的不断调用加加减减

最终回到开始位置

data里面的数据也都使用好了

最终的结果保存在ax寄存器中了

代码就结束了


相关文章
|
6月前
|
自然语言处理 中间件 编译器
C语言的编译器和中间件开发
C语言的编译器和中间件开发
|
6月前
|
设计模式 中间件 编译器
C语言编译器
C语言编译器
|
存储 C语言
【C语言】指针概要
【C语言】指针概要
64 0
|
1月前
|
编译器 C语言
C语言编译器为什么能够用C语言编写?
C语言编译器为什么能够用C语言编写?
39 9
|
1月前
|
存储 编译器 C语言
【C语言】探索结构体基础知识:简明概要
【C语言】探索结构体基础知识:简明概要
|
2月前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
编译器 Linux C语言
c语言的编译器vs2019的安装及简单实用
c语言的编译器vs2019的安装及简单实用
179 0
|
6月前
|
存储 编译器 程序员
C语言调试大作战:与VS编译器共舞,上演一场“捉虫记”的艺术与科学
C语言调试大作战:与VS编译器共舞,上演一场“捉虫记”的艺术与科学
|
6月前
|
编译器 C语言 Windows
windows MinGW C语言编译器安装及环境变量配置教程
MinGW被称为Windows版的GCC,安装包下载地址:提示:该安装包下载完之后,相当于安装好了MinGW,之后即可配置环境变量!所以,可以先新建好一个专门用来存放MinGW安装包的文件夹。
266 2
|
6月前
|
存储 编译器 C语言
【C语言必知必会 | 第二篇】编译器的安装与使用
【C语言必知必会 | 第二篇】编译器的安装与使用
74 0