我发起并创立了一个 C 语言编译器 开源项目 InnerC

本文是 VMBC / D#  项目 的 系列文章,

有关 VMBC / D# ,  见 《我发起并创立了一个 VMBC 的 子项目 D#》(以下简称 《D#》)  https://www.cnblogs.com/KSongKing/p/10348190.html    。

 

VMBC  需要一个 内置 的  C 编译器,   想来想去, 觉得还是自己写一个,

计划用  C 语言 写,  因为 VMBC 的  C 编译器 要求是一个 本地库,  如果不要求是 本地库,  我就用 C# 写了, 呵呵呵 。

 

为什么 是 库 呢 ?   因为这是一个 内置编译器,  是由  ILBC 运行时 来 调用的 (ILBC 见 《D#》),  所以 是一个 库  。

这个 库  最好 能 尽可能的  小 。

 

C 语言 写的 代码 是 最贴近 底层(汇编) 的,  所以  C 语言 写的 库 应该是 最紧凑 的, 所以用  C 语言 来写 。

 

还有一个 原因 是, 我会的 语言 不多,  C 算是 相对 更熟一点的,    So  。

 

有 网友 说 C 语言 不适合 写 编译器, C 的抽象太低了,  建议用 函数式 语言写,

又举例    Rust  最早是用  OCaml  写的,   然后又用 Rust 写了一遍  。

 

好吧,   但  Rust 、OCaml   这些语言 的 名字 我都 没怎么听过,   还是用 C 吧  。

另外用  C  的话, 应该不用担心 操作系统 的 支持 的 问题  。

 

这个 项目 我只 实现    语法分析   和   类型检查    的 部分,      语法分析 包含了 语法检查  。

生成目标代码     链接(链接外部库)  这  2 个 部分   大家 如果有兴趣, 对 汇编 和 操作系统 了解 的话, 可以来补充 。

 

InnerC  是   ansi C   的 子集 + 扩展, 只支持   ansi C   的 部分特性, 同时还会加入一些  新特性  。

总的来说,  InnerC  会 比  ansi C  简单 。

 

比如,  InnerC   不支持  结构体(Struct),  因为 InnerC 是 作为 中间语言, 只需要是一种  “高级汇编语言”  就可以 。

不用   Struct, 那用什么 ?

用 数组, 包括 静态数组 和 从 堆 里 分配 的 数组 。

根据 偏移量 向 数组 的 相应位置 写入 字段 的 值,  这就是 Struct,  也是 对象 。

去掉 Struct 可以 省掉 不少 语法分析 的 开销 和  人力上的 研发成本 。

 

但  C 语言 里好像没有 按值 传递 数组 的 特性, 所以  InnerC  需要 加入 按值传递数组(拷贝传递数组) 的 特性 。

比如, InnerC 应该 增加  T [ n ]  类型, 用于 参数 和 返回值,

T [ n ]  类型 表示 按值传递数组(拷贝传递数组),

 

假设 A() 方法 调用 B() 方法,  B() 方法有一个  T [ n ]  arr  参数,  那么 A() 方法 传给 T [ n ]  arr  参数 的 是一个 数组的 首地址 arr, 编译器会处理成 把 A() 里的 arr 数组 以 长度 n 拷贝到 B() 的 arr 里,  所以 B() 的  arr 也是 数组 的 首地址, 但是是 拷贝到 B() 的 堆栈 里的 数组 的 首地址 。 

T [ n ]  arr  表示 arr 参数 是 长度 为 n 的 数组, 编译器 会为 arr 在 B 的 堆栈 里 分配 长度为 n * sizeof(T)  的 内存空间 。 这个空间是 编译器 分配的, 是 静态分配 的,  等价于  声明一个   T arr[ n ]   这样的 静态数组 。

 

同理, 假设 B() 的 返回值 是 T [ n ]  类型,  B() 实际返回的是一个 数组 的 首地址 arr,  A() 里 用来 接收 B() 的 返回值 的 是一个    T arr[ n ]  arr ;   静态数组 变量, 编译器会处理成 把 B() 里的  arr 数组 以 长度 n 拷贝到 A() 的 arr 里 。

 

InnerC   也不支持 对 函数指针 进行 类型检查,

不对 函数指针 类型检查 是指 函数指针 可以调用 任意 的 参数列表,  当然, 出了错 是 调用者 自己 负责 。^^

不过 对于 中间语言 来说, 基本上 不用担心 这个问题 。

 

InnerC  的 语法分析 可以 生成一个 表达式对象树,  把 表达式对象树 序列化 得到一个    byte []  (byte 数组),

这个  byte[]    就相当于  .Net  的  Op Code,  或者  java  的  Byte Code,   我们可以把 这个  byte[]  称为   ILBC Byte Code (简称 Byte Code)  。

这样一来, 问题就明朗了,

如果  开发期 编译 生成的 目标代码 就是   ILBC Byte Code,   那  JIT 速度 较慢 的 问题 就 解决了  。

这就是说, 可以把   C 语言 作为 第一级 中间代码,   Byte Code 作为 第二级 中间代码  。

 

这样,  InnerC  就可以由  2 个模块 组成:

1   InnerC   to   Byte Code

2   Byte Code   to   Native Code

 

当然, 可以在 开发期 编译 直接 生成  Native Code (本地代码),     这是  AOT  。

 

 

 

转载于:https://www.cnblogs.com/KSongKing/p/10352273.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C0文法 <加法运算符> ::= +|- <乘法运算符> ::= * |/ <关系运算符> ::= <|<=|>|>=|!=|== <字符> ::= _|a|...|z|A|...|Z <数字> ::= 0|<非零数字> <非零数字> ::= 1|...|9 <字符串> ::= "{ }" //字符串中可以出现所有合法的可打印字符集中的字符 <程序> ::= [<常量说明部分>][<变量说明部分>]{<子函数定义部分>}<主函数> <常量说明部分> ::= const<常量定义>{,<常量定义>}; <常量定义> ::= <标识符>=<整数> <整数> ::= [+|-]<非零数字>{<数字>}|0 <标识符> ::= <字符>{<字符>|<数字>} <声明头部> ::= int <标识符> <变量说明部分> ::= <声明头部>{,<标识符>}; <子函数定义部分> ::= (<声明头部>|void <标识符>)<参数><复合语句> <复合语句> ::= ‘{’[<常量说明部分>][<变量说明部分>]<语句序列>‘}’ <参数> ::= ‘(’<参数表>‘)’ <参数表> ::= int<标识符>{,int<标识符>} | 空 <主函数> ::= (void |int) main <参数><复合语句> <表达式> ::= [+|-]<项>{<加法运算符><项>} <项> ::= <因子>{<乘法运算符><因子>} <因子> ::= <标识符>|‘(’<表达式>‘)’|<整数>|<子函数调用语句> <语句> ::= <条件语句>|<循环语句>|‘{’‘}’|<子函数调用语句>; |<赋值语句>; | ;|<读语句>;|<写语句>;|<空> <赋值语句> ::= <标识符>=<表达式> <条件语句> ::= if‘(’<条件>‘)’<语句>[else<语句>] <条件> ::= <表达式><关系运算符><表达式>|<表达式> <循环语句> ::= while‘(’<条件>‘)’<语句> <子函数调用语句> ::= <标识符>‘(’<值参数表>‘)’ <值参数表> ::= <表达式>{,<表达式>}|<空> <语句序列> ::= <语句>{<语句>} <读语句> ::= scanf‘(’<标识符>‘)’ <写语句> ::= printf‘(’[,][<表达式 >]‘)’ <返回语句> ::= return [ ‘(’<表达式>‘)’] 注:返回值为void类型的子函数不允许出现在表达式中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值