编译原理初识

为什么要了解编译技术:

  • 编译技术是计算机科学的一个非常重要的基础技术
  • 编程用的一些框架或组件都用到了编译技术,比如注解支持和字节码动态生成等
  • 帮助理解项目构建流程CMake 和 Gradle等配置构建工具配置
  • 其他等等....,绝对不是只能用于炫耀的屠龙技

本次分享的内容简介:

  • 主要是编译器前端部分
  • 理论部分涉及到词法分析,语法分析,语义分析
  • c4简版编译器(4个函数实现的c编译器,大约500行)源码分析,帮助理解编译原理

现代编译器结构

现代编译器通常分为前端和后端,前端将高级语言转化为中间的统一代码,然后由后端将中间代码编译为机器代码,处理硬件架构相关的优化,讲到现代编译器我们主要以LLVM平台作为学习重点,LLVM是Apple官方支持的编译器,LLVM是一个模块化的编译器平台,可以支持不同的编译前端,Apple官方的编译器前端是Clang,前端生成各种中间代码,后端自动进行各类优化分析,让用LLVM开发的编译器,都能用上最先进的后端优化技术。

image

LLVM发展历史:https://blog.csdn.net/zhouzhaoxiong1227/article/details/52166942

编译涉及到的主要流程如下:

image

词法分析

  • 词法分析:相当于识别每个词代表什么。进行的工作就是识别单词。在程序语言中,一般单词的类型包括:关键字,操作符,变量标识符,数值常量,字符串常量和运算符。

词法分析器以源码字符串为输入,输出为标记流(token stream),即一连串的标记,每个标记通常包括: (token, token value) 即标记本身和标记的值。

例如:

2 + 3 * (4 - 5)
=>
(Number, 2) Add (Number, 3) Multiply Left-Bracket (Number, 4) Subtract (Number, 5) Right-Bracket

由于词法分析的工作很常见,但又枯燥且容易出错,所以人们已经开发出了许多工具来生成词法分析器,如 lex, flex。这些工具允许我们通过正则表达式来识别标记。

这里注意的是,我们并不会一次性地将所有源码全部转换成标记流,原因:

  1. 字符串转换成标记流有时是有状态的,即与代码的上下文是有关系的。
  2. 保存所有的标记流没有意义且浪费空间。

所以实际的处理方法是提供一个函数(即前几篇中提到的 next()),每次调用该函数则返回下一个标记。

词法分析实践---c4

c4的词法分析实现采用的是手工编码实现方式,没有采用正则表达,因为支持的语法比较简单手写也不复杂。

char *p, *lp, // 指向source area的指针,读入的代码会以文本信息的形式读入在该区域 current position in source code
int64 *e, *le,  // 指向text area的指针,解析代码后生成相应的伪汇编代码写入该区域 current position in emitted code
    *id,      // 指向当前正在设置或解析的语义关键字结构 currently parsed identifier
    *sym,     // 指向symble area的指针,能够解析的关键字、库函数以及函数的参数和局部变量会存储在该区域 symbol table (simple list of identifiers)
    tk,       // 当前正在解析的代码的游标  current token
    ival,     // 当前解析的数字的值 current token value
    ty,       // 语义关键字结构的类型 current expression type
    loc,      // 局部变量偏移量标识 local variable offset
    line,     // 当前解析的代码行数 current line number
    src,      // 解析的代码和生成的伪汇编代码的标识 print source and assembly flag
    debug;    // 输出汇编代码执行结果标识 print executed instructions

// 获取Token,即词法分析
void next()
{
  char *pp;

  // 只要未到文件末尾
  while (tk = *p) {
    ++p;
    if (tk == '\n') {
      // 换行符处理
      if (src) {
        printf("line %d: %.*s", line, p - lp, lp);
        lp = p;
        while (le < e) {
          printf("%8.4s", &"LEA ,IMM ,JMP ,JSR ,BZ  ,BNZ ,ENT ,ADJ ,LEV ,LI  ,LC  ,SI  ,SC  ,PSH ,"
                           "OR  ,XOR ,AND ,EQ  ,NE  ,LT  ,GT  ,LE  ,GE  ,SHL ,SHR ,ADD ,SUB ,MUL ,DIV ,MOD ,"
                           "OPEN,READ,CLOS,PRTF,MALC,FREE,MSET,MCMP,EXIT,"[*++le * 5]);
          if (*le <= ADJ) printf(" %d\n", *++le); else printf("\n");
        }
      }
      ++line;
    }
    else if (tk == '#') {
      // #符处理,此处直接忽略
      while (*p != 0 && *p != '\n') ++p;
    }
    else if ((tk >= 'a' && tk <= 'z') || (tk >= 'A' && tk <= 'Z') || tk == '_') {
      // 字符处理,变量或函数名等
      pp = p - 1;
      // 第二个字符符合变量字符范围
      while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')
        tk = tk * 147 + *p++;
      tk = (tk << 6) + (p - pp);
      id = sym;
      // 得到变量或函数名的Token,p-pp范围内的字符
      while (id[Tk]) { // 查询符号表中是否已经有该变量定义,有直接返回
        if (tk == id[Hash] && !memcmp((char *)id[Name], pp, p - pp)) { tk = id[Tk]; return; }
        id = id + Idsz;
      }
      // 符号表中没有该定义,就添加到符号表中
      id[Name] = (int64)pp;
      id[Hash] = tk;
      tk = id[Tk] = Id;
      return;
    }
    else if (tk >= '0' && tk <= '9') {
      // 数字处理
      if (ival = tk - '0') { while (*p >= '0' && *p <= '9') ival = ival * 10 + *p++ - '0'; }
      else if (*p == 'x' || *p == 'X') {
        // 十六进制数字转化为十进制
        while ((tk = *++p) && ((tk >= '0' && tk <= '9') || (tk >= 'a' && tk <= 'f') || (tk >= 'A' && tk <= 'F')))
          ival = ival * 16 + (tk & 15) + (tk >= 'A' ? 9 : 0);
      }
      else { 
        // 八进制转为十进制
        while (*p >= '0' && *p <= '7') ival = ival * 8 + *p++ - '0'; 
      }
      tk = Num;
      return;
    }
    else if (tk == '/') {
      // ‘/’字符处理
      if (*p == '/') {
        // 下一个字符也是‘/’,是注释代码,直接忽略
        ++p;
        while (*p != 0 && *p != '\n') ++p;
      }
      else {
        // 除法
        tk = Div;
        return;
      }
    }
    else if (tk == '\'' || tk == '"') {
      // 字符串处理
      pp = data;
      while (*p != 0 && *p != tk) {
        if ((ival = *p++) == '\\') {
          if ((ival = *p++) == 'n') ival = '\n';
        }
        if (tk == '"') *data++ = ival;
      }
      ++p;
      if (tk == '"') ival = (int64)pp; else tk = Num;
      return;
    }
    else if (tk == '=') { if (*p == '=') { ++p; tk = Eq; } else tk = Assign; return; }
    else if (tk == '+') { if (*p == '+') { ++p; tk = Inc; } else tk = Add; return; }
    else if (tk == '-') { if (*p == '-') { ++p; tk = Dec; } else tk = Sub; return; }
    else if (tk == '!') { if (*p == '=') { ++p; tk = Ne; } return; }
    else if (tk == '<') { if (*p == '=') { ++p; tk = Le; } else if (*p == '<') { ++p; tk = Shl; } else tk = Lt; return; }
    else if (tk == '>') { if (*p == '=') { ++p; tk = Ge; } else if (*p == '>') { ++p; tk = Shr; } else tk = Gt; return; }
    else if (tk == '|') { if (*p == '|') { ++p; tk = Lor; } else tk = Or; return; }
    else if (tk == '&') { if (*p == '&') { ++p; tk = Lan; } else tk = And; return; }
    else if (tk == '^') { tk = Xor; return; }
    else if (tk == '%') { tk = Mod; return; }
    else if (tk == '*') { tk = Mul; return; } 
    else if (tk == '[') { tk = Brak; return; }
    else if (tk == '?') { tk = Cond; return; }
    else if (tk == '~' || tk == ';' || tk == '{' || tk == '}' || tk == '(' || tk == ')' || tk == ']' || tk == ',' || tk == ':') return;
  }
}

语法分析

语法分析:识别语句的结构,通常以树的形式表示。

如果把词法分析看作为字母组合成单词的过程,那么语法分析就是一个把单词组合成句子的过程。正如在词法分析中使用正则表达式来描述词法的规则一样,我们在语法分析中使用上下文无关文法来描述语言的语法规则。我们可以把某一种语言看成无数个符合语法规则的句子的集合,根据给定的上下文无关文法我们可以判断某一个 Token 串是否符合某个语法规则;如果符合,那么我们可以把此文法和对应输入的 Token 串组合起来生成一个句子。整个流程如下图所示:

image

上下文无关文法

上下文无关文法(context-free grammar, CFG),是一种用来对某种语言进行形式化的、精确的描述的工具,我们就可以很方便的定义一种语言的语法了。CFG 是一个四元组(N, T, P, S),下面我们看一下 CFG 的组成:

  • N 是非终结符的集合;
  • T 是终结符的集合;
  • P 是一条产生式规则;
  • S 是开始符号;

四则运算的语法分析器例子

四则运算的规则可以表示如下:

<S> -> <A> + <S>
         | <A> - <S> 
         | <A>

<A> -> <B> * <A>
         | <B> / <A>
         | <B>

<B> -> ( <S> )
           | Num

用尖括号 <> 括起来的就称作 非终结符 ,因为它们可以用->右侧的式子代替。| 表示选择,如 <S> 可以是 <S> + <A>、<S> - <A>或 <A> 中的一种。而没有出现在->左边的就称作 终结符 ,一般终结符对应于词法分析器输出的标记。

抽象语法树

抽象语法树是程序中语句表示的一种数据格式。

以上面的 CFG 为例,我们要从它推导出句子 1+2-3*4,则推导方式如下:

S => A+S => B+S => 1+S => 1+A-S => 1+B-S =>1+2-S =>1+2-A=>1+2-BA=>1+2-3A=>1+2-3B=>1+2-34,这个推导过程可以用下面的(语法)分析树来表示:

无法复制加载中的内容

语义分析

对语法分析生成的两个语法树进行分析,检查结构正确的句子是否语义合法,对于语言来说主要会做以下几点:

  • 申明检查
  • 类型检查
  • 范围解决方案
  • 数组绑定检查

符号表

虽然编译器很不厚道的给链接器留了一项任务,但是编译器为了链接器工作的轻松一点还是做了一点事情,这就是符号表,符号表是为编译器存储变量名、函数名、对象、类、接口等各种实体的出现情况而创建和维护的一种重要的数据结构,符号表是实现变量作用域的关键——常见做法是用hashtable来实现 (变量名 -> 变量信息) 的映射关系,并且通过嵌套的符号表来实现作用域。举个符号表样例:

image

看个例子,一个用Java实现的C的简化版C-flat语言的编译器cbc里的作用域实现:

https://github.com/aamine/cbc/blob/master/net/loveruby/cflat/entity/LocalScope.java

  • 它用Map<String, DefinedVariable>来记录本作用域的变量名映射关系;
  • 它用parent链来构成嵌套的作用域。变量名的搜索总是从当前作用域开始,找不到再到上一层作用域搜索,直到到达顶层作用域为止。

再看个简单的例子,巨紧凑的迷你C编译器c4里也有符号表:

https://github.com/rswier/c4

其中的int *sym就是符号表。不过c4不支持嵌套作用域,变量只支持函数级别(local)和全局级别(global)的作用域,所以处理起来相当简单:

  • 局部变量的处理:见 if (d[Class] == Loc)
  • 全局变量的处理:见 else if (d[Class] == Glo)
  • 还有一些特殊处理的命名空间,例如函数见 else if (d[Class] == Fun)

我们再详细说一下c4的符号表数据结构,它的结构是一个一维数组,符号的数据结构是含8个字段的对象,把连续的8个数据空间用于存一个符号记录。

int   *sym // 符号表指针

/** 符号表是一个长数组,每个数组的元素为符号描述,元素长度为Idsz
    每个符号元素包括的信息为:
    Tk 符号类型,id类型,用来区分是否为关键字的
    Hash 符号哈希
    Name 符号名字
    Class 变量类型,Num = 128(常数)Fun(函数)Sys(系统调用) Glo(全局变量) Loc(本地变量) Id
    Type 变量数值类型,char,int,ptr,还是*char,*int,*ptr,还是**char,**int,**ptr
    Val 变量的值,对应不同的数据类型有不同的值
*/
enum { Tk, Hash, Name, Class, Type, Val, HClass, HType, HVal, Idsz };

// 比如下面查询符号表中是否已有指定名字的符号,如果没有就添加一条记录到符号表中
id = sym;
while (id[Tk]) { // 遍历符号表
    if (tk == id[Hash] && !memcmp((char *)id[Name], pp, p - pp)) { 
        // 查询符号表中是否已经有该变量定义,有直接返回
        tk = id[Tk]; return; 
    }
    // 遍历下一个符号,Idsz是符号的对象的size=8
    id = id + Idsz;
}
// 符号表中没有该定义,就添加到符号表中
id[Name] = (int64)pp;
id[Hash] = tk;
tk = id[Tk] = Id;

有了符号表,上面提到的语义分析中的关键检查如下:

申明检查:申明类型,如果从符号表中查询未定义,报申明异常

类型检查:使用到的类型如果符号表中未查询到,报类型异常

范围解决方案:符号表通过嵌套实现作用域,变量作用域不对,报异常

数组绑定检查:查找符号表中的数组定义,并根据符号信息取对应数据

总的来说,有了符号表和抽象语法树,就可以做语法检查和中间代码生成。

语法语义分析实践---c4

c4是一个简版的编译器实现,它的语法分析、语义分析以及生成中间代码都是在两个函数中实现,且没有生成抽象语法树,直接语法分析+语义分析,并直接生成了中间代码,我们看看它的实现源码来帮助理解语法分析和语义分析,中间代码生成涉及到汇编相关知识不在本文范围。

// 代码解析
void stmt()
{
  int64 *a, *b;
        printf("statement() token=%c \n", tk);

  if (tk == If) {
    // if语句处理
    next();
    if (tk == '(') next(); else { printf("%d: open paren expected\n", line); exit(-1); }
    expr(Assign);
    if (tk == ')') next(); else { printf("%d: close paren expected\n", line); exit(-1); }
    *++e = BZ; b = ++e;
    stmt();
    if (tk == Else) {
      *b = (int64)(e + 3); *++e = JMP; b = ++e;
      next();
      stmt();
    }
    *b = (int64)(e + 1);
  }
  else if (tk == While) {
    // while语句处理
    next();
    a = e + 1;
    if (tk == '(') next(); else { printf("%d: open paren expected\n", line); exit(-1); }
    expr(Assign);
    if (tk == ')') next(); else { printf("%d: close paren expected\n", line); exit(-1); }
    *++e = BZ; b = ++e;
    stmt();
    *++e = JMP; *++e = (int64)a;
    *b = (int64)(e + 1);
  }
  else if (tk == Return) {
    // return处理
    next();
    if (tk != ';') expr(Assign);
    *++e = LEV;
    if (tk == ';') next(); else { printf("%d: semicolon expected\n", line); exit(-1); }
  }
  else if (tk == '{') {
    // 函数体
    next();
    while (tk != '}') stmt();
    next();
  }
  else if (tk == ';') {
    next();
  }
  else {
    // 表达式处理,关键!!!
    expr(Assign);
    if (tk == ';') next(); else { printf("%d: semicolon expected\n", line); exit(-1); }
  }
}

// 操作符语义解析(如:sizof,&,|,++,--,=,==等等) 生成伪汇编代码,写到text area中
void expr(int64 lev)
{
  int64 t, *d;

  if (!tk) { printf("%d: unexpected eof in expression\n", line); exit(-1); }
  else if (tk == Num) { 
    // 数字处理,生成对应的中间代码
    *++e = IMM; *++e = ival; next(); ty = INT;
  }
  else if (tk == '"') {
    // 字符串处理
    *++e = IMM; *++e = ival; next();
    while (tk == '"') next();
      // append the end of string character '\0', all the data are default
    // to 0, so just move data one position forward.
    data = (char *)((int64)data + sizeof(int64) & -sizeof(int64)); ty = PTR;
  }
  else if (tk == Sizeof) {
    next(); if (tk == '(') next(); else { printf("%d: open paren expected in sizeof\n", line); exit(-1); }
    ty = INT; if (tk == Int) next(); else if (tk == Char) { next(); ty = CHAR; }
    while (tk == Mul) { next(); ty = ty + PTR; }
    if (tk == ')') next(); else { printf("%d: close paren expected in sizeof\n", line); exit(-1); }
    *++e = IMM; *++e = (ty == CHAR) ? sizeof(char) : sizeof(int64);
    ty = INT;
  }
  else if (tk == Id) {
    d = id; next();
    if (tk == '(') {
      // 处理函数调用
      next();
      t = 0;
      while (tk != ')') { expr(Assign); *++e = PSH; ++t; if (tk == ',') next(); }
      next();
      if (d[Class] == Sys) *++e = d[Val];
      else if (d[Class] == Fun) { *++e = JSR; *++e = d[Val]; }
      else { printf("%d: bad function call\n", line); exit(-1); }
      if (t) { *++e = ADJ; *++e = t; }
      ty = d[Type];
    }
    else if (d[Class] == Num) { 
      // 处理数字
      *++e = IMM; *++e = d[Val]; ty = INT;
    }
    else {
      // 变量处理
      if (d[Class] == Loc) { *++e = LEA; *++e = loc - d[Val]; }
      else if (d[Class] == Glo) { *++e = IMM; *++e = d[Val]; }
      else { printf("%d: undefined variable\n", line); exit(-1); }
      *++e = ((ty = d[Type]) == CHAR) ? LC : LI;
    }
  }
  else if (tk == '(') {
    // 类型强转
    next();
    if (tk == Int || tk == Char) {
      t = (tk == Int) ? INT : CHAR; next();
      while (tk == Mul) { next(); t = t + PTR; }
      if (tk == ')') next(); else { printf("%d: bad cast\n", line); exit(-1); }
      expr(Inc);
      ty = t;
    }
    else {
      expr(Assign);
      if (tk == ')') next(); else { printf("%d: close paren expected\n", line); exit(-1); }
    }
  }
  else if (tk == Mul) {
    // 指针取值
    next(); expr(Inc);
    if (ty > INT) ty = ty - PTR; else { printf("%d: bad dereference\n", line); exit(-1); }
    *++e = (ty == CHAR) ? LC : LI;
  }
  else if (tk == And) {
    // 取址操作
    next(); expr(Inc);
    if (*e == LC || *e == LI) --e; else { printf("%d: bad address-of\n", line); exit(-1); }
    ty = ty + PTR;
  }
  else if (tk == '!') {
    //逻辑取反
   next(); expr(Inc); *++e = PSH; *++e = IMM; *++e = 0; *++e = EQ; ty = INT; 
 }
  else if (tk == '~') {
    // 按位取反
   next(); expr(Inc); *++e = PSH; *++e = IMM; *++e = -1; *++e = XOR; ty = INT; 
 }
  else if (tk == Add) {
    // 正号
   next(); expr(Inc); ty = INT; }
  else if (tk == Sub) {
    // 负号
    next(); *++e = IMM;
    if (tk == Num) { *++e = -ival; next(); } else { *++e = -1; *++e = PSH; expr(Inc); *++e = MUL; }
    ty = INT;
  }
  else if (tk == Inc || tk == Dec) {
    // 自增自减
    t = tk; next(); expr(Inc);
    if (*e == LC) { *e = PSH; *++e = LC; }
    else if (*e == LI) { *e = PSH; *++e = LI; }
    else { printf("%d: bad lvalue in pre-increment\n", line); exit(-1); }
    *++e = PSH;
    *++e = IMM; *++e = (ty > PTR) ? sizeof(int64) : sizeof(char);
    *++e = (t == Inc) ? ADD : SUB;
    *++e = (ty == CHAR) ? SC : SI;
  }
  else { printf("%d: bad expression\n", line); exit(-1); }

  while (tk >= lev) { // "precedence climbing" or "Top Down Operator Precedence" method
    t = ty;
    if (tk == Assign) {
      // 赋值操作
      next();
      if (*e == LC || *e == LI) *e = PSH; else { printf("%d: bad lvalue in assignment\n", line); exit(-1); }
      expr(Assign); *++e = ((ty = t) == CHAR) ? SC : SI;
    }
    else if (tk == Cond) {
      // 三目运算符 ? : 
      next();
      *++e = BZ; d = ++e;
      expr(Assign);
      if (tk == ':') next(); else { printf("%d: conditional missing colon\n", line); exit(-1); }
      *d = (int64)(e + 3); *++e = JMP; d = ++e;
      expr(Cond);
      *d = (int64)(e + 1);
    }
    // 逻辑运算符
    else if (tk == Lor) { next(); *++e = BNZ; d = ++e; expr(Lan); *d = (int64)(e + 1); ty = INT; }
    else if (tk == Lan) { next(); *++e = BZ;  d = ++e; expr(Or);  *d = (int64)(e + 1); ty = INT; }
    else if (tk == Or)  { next(); *++e = PSH; expr(Xor); *++e = OR;  ty = INT; }
    else if (tk == Xor) { next(); *++e = PSH; expr(And); *++e = XOR; ty = INT; }
    else if (tk == And) { next(); *++e = PSH; expr(Eq);  *++e = AND; ty = INT; }
    else if (tk == Eq)  { next(); *++e = PSH; expr(Lt);  *++e = EQ;  ty = INT; }
    else if (tk == Ne)  { next(); *++e = PSH; expr(Lt);  *++e = NE;  ty = INT; }
    else if (tk == Lt)  { next(); *++e = PSH; expr(Shl); *++e = LT;  ty = INT; }
    else if (tk == Gt)  { next(); *++e = PSH; expr(Shl); *++e = GT;  ty = INT; }
    else if (tk == Le)  { next(); *++e = PSH; expr(Shl); *++e = LE;  ty = INT; }
    else if (tk == Ge)  { next(); *++e = PSH; expr(Shl); *++e = GE;  ty = INT; }
    else if (tk == Shl) { next(); *++e = PSH; expr(Add); *++e = SHL; ty = INT; }
    else if (tk == Shr) { next(); *++e = PSH; expr(Add); *++e = SHR; ty = INT; }
    // 数学运算符
    else if (tk == Add) {
      // + 
      next(); *++e = PSH; expr(Mul);
      if ((ty = t) > PTR) { *++e = PSH; *++e = IMM; *++e = sizeof(int64); *++e = MUL;  }
      *++e = ADD;
    }
    else if (tk == Sub) {
      // -
      next(); *++e = PSH; expr(Mul);
      if (t > PTR && t == ty) { *++e = SUB; *++e = PSH; *++e = IMM; *++e = sizeof(int64); *++e = DIV; ty = INT; }
      else if ((ty = t) > PTR) { *++e = PSH; *++e = IMM; *++e = sizeof(int64); *++e = MUL; *++e = SUB; }
      else *++e = SUB;
    }
    else if (tk == Mul) { 
      // * 
      next(); *++e = PSH; expr(Inc); *++e = MUL; ty = INT; 
    }
    else if (tk == Div) {
      // /
     next(); *++e = PSH; expr(Inc); *++e = DIV; ty = INT;
      }
    else if (tk == Mod) { next(); *++e = PSH; expr(Inc); *++e = MOD; ty = INT; }
    else if (tk == Inc || tk == Dec) {
      // 自增自减
      if (*e == LC) { *e = PSH; *++e = LC; }
      else if (*e == LI) { *e = PSH; *++e = LI; }
      else { printf("%d: bad lvalue in post-increment\n", line); exit(-1); }
      *++e = PSH; *++e = IMM; *++e = (ty > PTR) ? sizeof(int64) : sizeof(char);
      *++e = (tk == Inc) ? ADD : SUB;
      *++e = (ty == CHAR) ? SC : SI;
      *++e = PSH; *++e = IMM; *++e = (ty > PTR) ? sizeof(int64) : sizeof(char);
      *++e = (tk == Inc) ? SUB : ADD;
      next();
    }
    else if (tk == Brak) {
      // 数组取值操作
      next(); *++e = PSH; expr(Assign);
      if (tk == ']') next(); else { printf("%d: close bracket expected\n", line); exit(-1); }
      if (t > PTR) { *++e = PSH; *++e = IMM; *++e = sizeof(int64); *++e = MUL;  }
      else if (t < PTR) { printf("%d: pointer type expected\n", line); exit(-1); }
      *++e = ADD;
      *++e = ((ty = t - PTR) == CHAR) ? LC : LI;
    }
    else { printf("%d: compiler error tk=%d\n", line, tk); exit(-1); }
  }
}

c4总结

c4仓库:https://github.com/rswier/c4

c4的代码简洁,但较难懂,好多变量都是缩写。

功能齐全,满足编译器开发的,词法分析,语法分析,目标代码生成的三个基本步骤。

并实现了自举,可以自己编译自己。

// 编译c4
gcc -o c4 c4.c

// 用c4编译hello.c文件
./c4 hello.c

// 用c4编译hello.c文件, 输出源码参数
./c4 -s hello.c

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

推荐阅读更多精彩内容