【从汇编语言到C语言编辑器入门笔记10(完结)】 - 可自举的C语言编译器源码解读
C4编译器是一个极简的C语言编译器实现,仅用四个核心函数(next/expr/stmt/main)完成从词法分析到代码生成的全流程。该项目包含编译器核心c4.c、测试程序hello.c等文件,采用GPLv2协议。c4.c通过全局变量传递状态,使用优先级攀升法处理表达式语法,将编译与执行结合,实现了一个可自编译的基础C编译器。其设计精简高效,支持基本数据类型、控制语句等核心特性,能够编译自身代码并执
C4编译器源码解读
GitHub项目地址:https://github.com/rswier/c4
这个仓库包含一个名为 c4 的开源项目,其核心是一个极简主义的 C 语言编译器实现,特点是可以自举且仅用四个函数完成相关功能,代码很简洁优美,值得给那位作者点个Star。
主要文件
- c4.c:编译器的核心实现文件,包含了词法分析、语法分析、代码生成等功能,能够处理 C 语言中的字符、整数、指针类型,以及 if、while、return 等语句和表达式,具备足够的特性以支持自编译等操作。
- hello.c:一个简单的 C 语言示例程序,内容为输出 “hello, world”,可用于测试编译器。
- README.md:提供了项目的基本信息和使用方法,例如通过
gcc -o c4 c4.c编译生成可执行文件,然后可以用生成的c4来编译运行 hello.c 等。 - LICENSE:采用 GNU General Public License Version 2 协议,该协议保障了用户分享和修改软件的自由,明确了复制、分发和修改程序的条款和条件,包括对源代码分发的要求、专利问题的处理、免责声明等内容。
c4.c 是一个极简主义的 C 语言编译器实现,仅通过四个核心函数(next()、expr()、stmt()、main())完成从 C 源代码解析到指令生成再到执行的全流程。它本质上是一个 “编译器 + 虚拟机” 的结合体,能将简单的 C 代码编译为自定义虚拟机指令并直接执行。
C4代码结构
一、核心全局变量与枚举
1. 全局变量
这些变量用于在编译器各阶段传递状态(词法分析、语法分析、代码生成等):
2. 枚举定义
用于统一标识 token、指令、类型等,避免 “魔法数字”:
- token 类型:包含常量、标识符、关键字、运算符等,运算符按优先级排序(优先级从低到高)
- 虚拟机指令(opcode):编译器生成的目标指令,由虚拟机执行
- 数据类型:标识变量或表达式的类型
- 符号表偏移量:由于未使用结构体,用偏移量表示标识符的属性(符号表是线性数组,每个标识符占Idsz大小)
二、词法分析:next() 函数
作用:将源代码字符串拆分为一个个有意义的 token(如关键字、标识符、常量、运算符),并更新全局变量 tk(token 类型)和 ival(token 值)。
核心逻辑:
- 跳过无关字符:忽略空格、换行(换行时若
src=1则打印源码和汇编)、注释(//或#开头,跳过到行尾)。 - 识别标识符与关键字:
- 以字母 / 下划线开头,后续接字母 / 数字 / 下划线的字符序列视为标识符。
- 通过哈希算法(
tk = tk * 147 + *p++)计算标识符的哈希值,用于符号表查找。 - 查符号表:若已存在(哈希值和名称匹配),则
tk设为符号表中记录的类型(如关键字If);否则加入符号表,tk设为Id。
- 识别常量:
- 数字常量:支持十进制(
123)、十六进制(0x1a)、八进制(077),转换为整数存入ival,tk设为Num。 - 字符 / 字符串常量:单引号(字符)或双引号(字符串)包裹的内容,处理转义字符(如
\n转为换行符)。字符串存入数据区data,ival记录其地址;字符直接存值,tk设为Num。
- 数字常量:支持十进制(
- 识别运算符与分隔符:
- 单字符运算符(如
+、*)直接返回。 - 双字符运算符(如
==、++、<=)通过检查下一个字符确定(如=后接=则为Eq,否则为Assign)。 - 分隔符(如
;、{、()直接作为tk返回。
- 单字符运算符(如
三、语法解析与代码生成:expr(int lev) 函数
作用:处理表达式的语法分析(验证语法结构),并同步生成对应的虚拟机指令(语法制导翻译)。采用 优先级攀升法 处理运算符优先级,lev 为当前需要处理的最低运算符优先级。
核心逻辑:
- 处理基础表达式单元(递归入口):
- 数字 / 字符串常量:生成
IMM指令(加载立即数或字符串地址)。 sizeof运算符:解析类型(如sizeof(int)),生成IMM指令返回类型大小(sizeof(char)或sizeof(int))。- 标识符:
- 若后续是
(,视为函数调用:解析参数表达式,生成JSR(跳转函数)和ADJ(调整栈参数)指令。 - 否则视为变量:根据类别(局部
Loc/ 全局Glo)生成LEA(加载地址)或LI/LC(加载值,区分int/char)指令。
- 若后续是
- 括号表达式:递归解析括号内的表达式。
- 一元运算符(
*解引用、&取地址、!逻辑非、++/--自增 / 减等):生成对应指令(如解引用用LI/LC,取地址提升为指针类型)。
- 数字 / 字符串常量:生成
- 处理运算符优先级(循环处理当前优先级及更高的运算符):
- 按
lev筛选运算符(如lev=Assign时处理赋值及更高优先级运算符)。 - 对每个运算符生成对应指令:
- 赋值(
Assign):解析左值和右值,生成SC/SI(存储值,区分char/int)。 - 条件表达式(
Cond,即? :):生成BZ(为零跳转)和JMP(无条件跳转)实现分支。 - 逻辑运算(
Lor/Lan):通过BNZ/BZ实现短路求值。 - 算术 / 比较运算(
+、-、==等):生成ADD、SUB、EQ等指令。 - 数组访问(
Brak,即[]):解析下标表达式,生成地址计算和加载指令。
- 赋值(
- 按
四、语句解析与代码生成:stmt() 函数
作用:处理 C 语言的基本语句(if、while、return、复合语句等),生成对应的控制流指令。
核心逻辑:
if语句:- 解析条件表达式,生成
BZ指令(条件为假时跳转到else或语句结束)。 - 解析
if体和else体(若有),生成JMP指令跳过else体,最后修正跳转地址。
- 解析条件表达式,生成
while循环:- 记录循环开始地址,解析条件表达式,生成
BZ指令(条件为假时退出循环)。 - 解析循环体,生成
JMP指令跳回循环开始,修正退出跳转的地址。
- 记录循环开始地址,解析条件表达式,生成
return语句:- 解析返回值表达式(若有),生成
LEV指令(函数退出,恢复栈帧)。
- 解析返回值表达式(若有),生成
- 复合语句(
{}):递归解析括号内的所有语句,直到}。 - 空语句(
;):直接跳过。 - 表达式语句:解析表达式后,检查并跳过结尾的
;。
五、主函数:main() 函数
作用:程序入口,负责初始化环境、读取源代码、解析声明(变量 / 函数)、生成指令,并作为虚拟机执行指令。
核心逻辑:
- 初始化:
- 解析命令行选项(
-s打印源码和汇编,-d打印执行指令)。 - 分配内存池:符号表
sym、指令流e、数据区data、虚拟机栈sp。 - 初始化符号表:预定义关键字(
char、if等)和系统调用(printf、malloc等,对应虚拟机指令PRTF、MALC等)。
- 解析命令行选项(
- 读取源代码:打开源文件,读取内容到内存。
- 解析声明:
- 全局变量 / 函数声明:解析基础类型(
int/char)和指针(*),记录到符号表(Class=Glo全局变量,Class=Fun函数)。 - 函数定义:解析参数和局部变量(
Class=Loc),生成函数入口指令ENT(初始化栈帧),调用stmt()解析函数体,生成函数退出指令LEV(恢复栈帧)。 - 枚举类型:解析枚举常量,存入符号表(
Class=Num,值为自增整数)。
- 全局变量 / 函数声明:解析基础类型(
- 虚拟机执行:
- 找到
main函数的指令入口,初始化虚拟机状态(程序计数器pc、栈指针sp、基指针bp)。 - 循环执行指令:根据指令码(
opcode)模拟操作(如LEA加载地址、JSR调用函数、ADD加法等),直到执行EXIT指令退出。
- 找到
总结
c4.c 是一个 “可自举” 的极简编译器:
- 词法分析(
next())将源码拆分为token; - 语法分析与代码生成(
expr()、stmt())将token序列转换为虚拟机指令; - 虚拟机(
main()后半部分)直接执行生成的指令。
其设计极致精简,通过全局变量传递状态,用优先级攀升法处理表达式,将编译与执行结合,实现了一个可自编译的基础 C 编译器核心。c4.c 是一个极简主义的 C 语言编译器实现,仅通过四个核心函数(next()、expr()、stmt()、main())完成从 C 源代码解析到指令生成再到执行的全流程。它本质上是一个 “编译器 + 虚拟机” 的结合体,能将简单的 C 代码编译为自定义虚拟机指令并直接执行。
代码解读
开头注释
- 文件标识与核心功能
// c4.c - C in four functions表明该文件名为 c4.c,且核心特点是通过四个函数实现了精简的C语言编译器 - 支持的语言特性
接下来的注释列出了该工具支持的 C 语言特性:- 基本数据类型:
char、int和指针类型 - 控制流语句:
if、while、return以及表达式语句 - 功能定位:“just enough features to allow self-compilation and a bit more” 说明其设计目标是支持足够的特性,以实现 “自编译”(即能够编译自身代码),并额外支持一些其他功能。
- 基本数据类型:
- 作者信息
// Written by Robert Swierczek注明了该程序的作者是 Robert Swierczek。
// c4.c - C in four functions
// char, int, and pointer types
// if, while, return, and expression statements
// just enough features to allow self-compilation and a bit more
// Written by Robert Swierczek
// c4.c - 用四个函数实现的 C 语言
// 字符型、整型和指针类型
// if、while、return 语句以及表达式语句
// 具备足够的特性以实现自编译,甚至更多功能
// 作者:罗伯特·斯维尔切克
文件引入
起始部分,主要包含头文件引入和类型定义,具体含义如下:
// 引入标准输入输出库,提供 printf、fopen 等函数
#include <stdio.h>
// 引入标准库,提供 malloc、free、exit 等内存管理和程序控制函数
#include <stdlib.h>
// 引入内存操作库,提供 memset 等内存初始化函数
#include <memory.h>
// 引入 Unix 标准库,提供 read、write、close 等系统调用函数
#include <unistd.h>
// 引入文件控制库,提供 open 等文件操作函数
#include <fcntl.h>
// 将 int 类型重定义为 long long,确保整数类型有足够的长度存储地址和数值
#define int long long
核心全局变量与枚举类型的定义
1.全局变量
char *p, *lp; // p:当前解析的源代码指针;lp:行起始指针(用于打印源码)
char *data; // 数据区指针(存储字符串常量、全局变量等)
int *e, *le; // e:当前生成的指令指针;le:指令流的临时指针(用于打印汇编)
int *id; // 当前解析的标识符在符号表中的位置
int *sym; // 符号表(存储标识符信息:关键字、变量、函数等)
int tk; // 当前词法分析得到的 token 类型(如 Num、Id、If 等)
int ival; // 当前 token 的值(如数字常量的值、字符串地址等)
int ty; // 当前表达式的类型(CHAR、INT、PTR)
int loc; // 局部变量的栈偏移量
int line; // 当前源代码的行号(用于错误提示)
int src; // 标记:是否打印源代码和汇编指令(-s 选项)
int debug; // 标记:是否打印虚拟机执行的指令(-d 选项)
2. 定义枚举
用于统一标识 token、指令、类型等,避免 “魔法数字”:
魔法数字:就是没“名字”、没“说明”的硬编码数字。解决它的核心思路是:把数字变成“有意义的符号”,让代码“自己说明自己”。这样不仅别人能看懂,未来的自己改代码时也能少踩坑。
token 类型:包含常量、标识符、关键字、运算符等,运算符按优先级排序(优先级从低到高):
enum {
// 基础类型与符号类别(128及以上避免与ASCII字符冲突)
Num = 128, // Number(数字常量):表示整数、字符常量等字面量
Fun, // Function(函数):标识符号表中的函数类型
Sys, // System(系统函数):标识系统内置函数(如printf、open等)
Glo, // Global(全局变量):标识全局变量
Loc, // Local(局部变量):标识局部变量
Id, // Identifier(标识符):未识别的变量名、函数名等符号
// 关键字(C语言保留字)
Char, // char:字符类型关键字
Else, // else:条件语句关键字
Enum, // enum:枚举类型关键字
If, // if:条件语句关键字
Int, // int:整数类型关键字
Return, // return:函数返回关键字
Sizeof, // sizeof:获取类型/变量大小的运算符
While, // while:循环语句关键字
// 运算符(按优先级从低到高排序,用于表达式解析的" precedence climbing"算法)
Assign, // Assignment(赋值运算符:=)
Cond, // Conditional(条件运算符:? :)
Lor, // Logical OR(逻辑或:||)
Lan, // Logical AND(逻辑与:&&)
Or, // Bitwise OR(按位或:|)
Xor, // Bitwise XOR(按位异或:^)
And, // Bitwise AND(按位与:&)
Eq, // Equal(等于:==)
Ne, // Not Equal(不等于:!=)
Lt, // Less Than(小于:<)
Gt, // Greater Than(大于:>)
Le, // Less or Equal(小于等于:<=)
Ge, // Greater or Equal(大于等于:>=)
Shl, // Shift Left(左移:<<)
Shr, // Shift Right(右移:>>)
Add, // Addition(加:+)
Sub, // Subtraction(减:-)
Mul, // Multiplication(乘:*)
Div, // Division(除:/)
Mod, // Modulus(取模:%)
Inc, // Increment(自增:++)
Dec, // Decrement(自减:--)
Brak // Bracket(方括号:[],用于数组访问)
};
虚拟机指令(opcode):编译器生成的目标指令,由虚拟机执行:
enum {
LEA , // Load Effective Address(加载有效地址):获取变量/内存的地址
IMM , // Immediate(立即数):加载常量值或全局地址
JMP , // Jump(无条件跳转):直接跳转到指定地址执行
JSR , // Jump to Subroutine(子程序跳转):调用函数(保存返回地址到栈)
BZ , // Branch if Zero(零值分支):结果为0时跳转
BNZ , // Branch if Not Zero(非零分支):结果非0时跳转
ENT , // Enter(进入子程序):函数调用时初始化栈帧(保存基址指针)
ADJ , // Adjust(栈调整):调整栈空间(通常用于清理函数参数)
LEV , // Leave(离开子程序):函数返回时恢复栈帧(恢复基址指针)
LI , // Load Integer(加载整数):从内存地址加载int类型值
LC , // Load Character(加载字符):从内存地址加载char类型值
SI , // Store Integer(存储整数):将int类型值存入内存地址
SC , // Store Character(存储字符):将char类型值存入内存地址
PSH , // Push(入栈):将值压入栈
OR , // Bitwise OR(按位或)
XOR , // Bitwise XOR(按位异或)
AND , // Bitwise AND(按位与)
EQ , // Equal(等于):比较两值是否相等
NE , // Not Equal(不等于):比较两值是否不等
LT , // Less Than(小于)
GT , // Greater Than(大于)
LE , // Less or Equal(小于等于)
GE , // Greater or Equal(大于等于)
SHL , // Shift Left(左移)
SHR , // Shift Right(右移)
ADD , // Addition(加法)
SUB , // Subtraction(减法)
MUL , // Multiplication(乘法)
DIV , // Division(除法)
MOD , // Modulus(取模)
OPEN, // Open(打开文件):对应系统调用open
READ, // Read(读取文件):对应系统调用read
CLOS, // Close(关闭文件):对应系统调用close
PRTF, // Print Formatted(格式化输出):对应printf
MALC, // Malloc(分配内存):对应malloc
FREE, // Free(释放内存):对应free
MSET, // Memset(内存设置):对应memset
MCMP, // Memcmp(内存比较):对应memcmp
EXIT // Exit(退出程序):对应exit
};
数据类型:标识变量或表达式的类型:
enum {
CHAR, // 字符类型(对应C语言的char)
INT, // 整数类型(对应C语言的int)
PTR // 指针类型(指向其他类型的指针,通过与CHAR/INT组合表示具体指针类型,如CHAR+PTR表示char*)
};
符号表偏移量:由于未使用结构体,用偏移量表示标识符的属性(符号表是线性数组,每个标识符占 Idsz 大小):
enum {
Tk, // Token(标记类型):标识该符号的类型(如关键字、标识符等,对应之前的Num/Id/If等)
Hash, // Hash Value(哈希值):标识符字符串的哈希值,用于快速查找符号表
Name, // Name Address(名称地址):标识符字符串在源代码中的内存地址
Class, // Class(类别):符号类别(如Fun函数/Glo全局变量/Loc局部变量/Sys系统函数等)
Type, // Type(类型):符号的数据类型(对应CHAR/INT/PTR及组合)
Val, // Value(值):符号的值(变量地址/函数入口地址/常量值等)
HClass, // Hidden Class(隐藏类别):临时保存原类别(用于函数参数/局部变量的作用域恢复)
HType, // Hidden Type(隐藏类型):临时保存原类型(同上)
HVal, // Hidden Value(隐藏值):临时保存原值(同上)
Idsz // Identifier Size(标识符大小):单个符号在符号表中占用的空间(数组元素个数)
};
next()函数
next() 函数是 c4.c 中负责词法分析(Tokenization) 的核心函数,其作用是从源代码字符串中逐个解析出标记(Token)(如关键字、标识符、数字、运算符等),并为后续的语法分析和代码生成提供基础。
核心功能
- 从全局指针
p指向的当前源代码位置开始,读取并识别一个完整的 Token。 - 将识别出的 Token 类型存储在全局变量
tk中,若为数字或字符串等有值的 Token,其值存储在ival中。 - 处理注释、换行等特殊字符,更新源代码位置指针
p和行号line。
关键变量说明
tk:全局变量,存储当前解析出的 Token 类型(对应之前枚举中的Num、Id、If、Add等)。p:全局指针,指向当前正在解析的源代码位置。ival:全局变量,存储当前 Token 的值(如数字的数值、字符串的地址等)。line:全局变量,记录当前源代码的行号,用于错误提示。src:全局标志,若为1,则在换行时打印源代码和生成的汇编指令(调试用)。sym:符号表(数组模拟),存储已识别的标识符(如变量名、函数名、关键字)。id:当前解析到的标识符在符号表中的位置。
// next() 函数:词法分析器核心函数,用于从源代码中逐个解析出标记(Token)
// 功能:读取源代码字符,识别关键字、标识符、数字、运算符等,并更新全局状态(如当前标记tk、值ival等)
void next()
{
char *pp; // 临时指针,用于存储标识符、字符串等的起始位置
// 主循环:从当前位置p读取字符,赋值给tk(循环终止条件:读取到字符串结束符'\0')
while (tk = *p) {
++p; // 指针后移,指向下一步要解析的字符
// 处理换行符'\n'
if (tk == '\n') {
if (src) { // 若开启源码打印模式(-s选项)
// 打印当前行号和源代码内容(从行起始指针lp到当前位置p)
printf("%d: %.*s", line, p - lp, lp);
lp = p; // 更新行起始指针为当前位置(下一行的开始)
// 打印当前生成的汇编指令(从le到e之间的指令)
while (le < e) {
// 从预定义指令字符串中截取4字符指令名(利用枚举顺序和字符串索引,每个指令占5字节,含逗号和空格)
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]);
// 对带参数的指令(如LEA、IMM等,编号<=ADJ)额外打印参数
if (*le <= ADJ) printf(" %d\n", *++le); else printf("\n");
}
}
++line; // 行号递增,用于错误提示和源码打印
}
// 处理预处理器指令(以'#'开头,如#include):跳过整行
else if (tk == '#') {
while (*p != 0 && *p != '\n') ++p; // 指针移动到行尾或文件结束
}
// 处理标识符和关键字(以字母或下划线开头)
else if ((tk >= 'a' && tk <= 'z') || (tk >= 'A' && tk <= 'Z') || tk == '_') {
pp = p - 1; // 记录标识符的起始位置(当前p已后移,故-1)
// 读取完整标识符(包含字母、数字、下划线),同时计算哈希值
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')
tk = tk * 147 + *p++; // 哈希算法:累加字符并乘系数147,减少哈希冲突
tk = (tk << 6) + (p - pp); // 哈希值结合标识符长度,进一步降低冲突概率
// 在符号表中查找该标识符
id = sym; // 从符号表起始位置开始遍历
while (id[Tk]) { // 符号表项的Tk字段为0时表示表尾
// 哈希值匹配且字符串内容完全一致(避免哈希冲突导致误判)
if (tk == id[Hash] && !memcmp((char *)id[Name], pp, p - pp)) {
tk = id[Tk]; // 更新当前标记为符号表中记录的类型(如关键字、函数等)
return; // 解析完成,返回
}
id = id + Idsz; // 移动到下一个符号表项(Idsz为每个表项的大小)
}
// 符号表中未找到:新增标识符到符号表
id[Name] = (int)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';
}
// 十六进制数字(0x或0X开头)
else if (*p == 'x' || *p == 'X') {
// 读取0-9、a-f、A-F字符,计算十六进制值
while ((tk = *++p) && ((tk >= '0' && tk <= '9') || (tk >= 'a' && tk <= 'f') || (tk >= 'A' && tk <= 'F')))
ival = ival * 16 + (tk & 15) + (tk >= 'A' ? 9 : 0); // 转换为数值(tk&15获取0-15,A-F加9)
}
// 八进制数字(0开头,无x)
else {
// 读取0-7字符,计算八进制值
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; // 解析完成,返回
}
}
// 处理字符常量('c')和字符串常量("str")
else if (tk == '\'' || tk == '"') {
pp = data; // 记录字符串在数据区的起始位置
// 读取到闭合引号为止
while (*p != 0 && *p != tk) {
ival = *p++; // 读取字符
if (ival == '\\') { // 处理转义字符(仅支持\n示例)
ival = *p++; // 读取转义后的字符
if (ival == 'n') ival = '\n'; // 转换\n为换行符
}
if (tk == '"') *data++ = ival; // 字符串常量存入数据区
}
++p; // 跳过闭合引号
if (tk == '"') {
ival = (int)pp; // 字符串常量的值为数据区起始地址
} else {
tk = Num; // 字符常量标记为数字(ASCII值)
}
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; // 直接返回,tk即为该字符本身
}
}
expr(int lev) 函数
核心内容
expr 函数是表达式解析器,基于 “优先级攀升法(precedence climbing)” 解析 C 语言风格的表达式,生成对应的中间代码(指令),并进行类型检查和错误处理。其核心逻辑分为两部分:
- 处理基础表达式元素
通过分支判断处理最基本的表达式单元(如常量、变量、函数调用、单目运算符等),生成初始中间指令,并确定当前表达式的类型(ty)。具体包括:- 数字(
Num):生成IMM(立即数)指令,类型设为INT。 - 字符串(
"):生成IMM指令存储字符串地址,类型设为PTR(指针)。 sizeof关键字:解析类型(int/char或指针),生成对应大小的IMM指令,类型设为INT。- 标识符(
Id):区分函数调用(生成JSR或系统函数指令)和变量访问(生成LEA/IMM加载地址,再用LI/LC加载值)。 - 括号(
():处理括号内表达式或类型转换(如(int)x),递归解析并更新类型。 - 单目运算符(
*/&/!/~/+/-/++/--):生成对应运算指令,检查类型合法性(如解引用需指针类型)。
- 数字(
- 处理运算符优先级(核心循环)
通过while (tk >= lev)循环,按运算符优先级(lev控制最低优先级)递归处理子表达式,生成运算指令。优先级从低到高覆盖:- 赋值运算符(
Assign):生成SC/SI(存储)指令,检查左值合法性。 - 条件运算符(
Cond,即? :):生成分支跳转指令(BZ/JMP)。 - 逻辑运算(
Lor/Lan,即||/&&):生成短路求值的条件跳转指令。 - 位运算(
Or/Xor/And)、比较运算(Eq/Ne/Lt等)、移位运算(Shl/Shr):生成对应运算指令(OR/EQ/SHL等)。 - 算术运算(
Add/Sub/Mul等):生成算术指令,支持指针运算(如指针加减整数时自动乘以元素大小)。 - 后缀自增 / 自减(
Inc/Dec):生成栈操作和更新指令,返回原始值。 - 数组访问(
Brak,即[]):计算数组元素地址,生成加载指令(LI/LC)。
- 赋值运算符(
关键变量
lev:当前处理的最低运算符优先级,控制循环解析的运算符范围(优先级高于或等于lev的运算符才会被处理)。t:临时变量,用于存储中间类型(如原始类型ty的备份)或临时 token(如自增 / 自减类型)。d:指针,指向符号表条目(id)或中间代码地址(用于分支跳转指令的地址回填)。tk:当前词法单元(来自next()函数的词法分析结果),决定当前解析的表达式元素或运算符。ival:当前tk的值(如数字的数值、字符串的地址),用于生成IMM指令。ty:当前表达式的类型(CHAR/INT/PTR),用于类型检查(如解引用必须是指针类型PTR)。e:指向当前生成的中间代码的指针,每生成一条指令(或操作数)就向前移动,记录代码生成位置。
**
* expr - 解析C语言表达式并生成对应中间代码
* @lev: 最低运算符优先级(控制当前解析的运算符范围,基于优先级攀升算法)
*
* 功能:处理各类表达式(常量、变量、运算符、函数调用、数组访问等),
* 根据运算符优先级生成中间指令,并进行类型检查和错误处理。
* 核心逻辑分为两部分:解析基础表达式单元 + 处理运算符优先级。
*/
void expr(int lev)
{
int t; // 临时变量:存储中间类型、运算符类型等
int *d; // 临时指针:指向符号表条目或中间代码地址(用于跳转地址回填)
// 检查是否已到源码末尾(未预期的文件结束)
if (!tk) {
printf("%d: unexpected eof in expression\n", line);
exit(-1);
}
// 1. 处理基础表达式单元(原子表达式)
// 1.1 数值常量(如123、0x1a等,词法分析已识别为Num)
else if (tk == Num) {
*++e = IMM; // 生成"立即数加载"指令(IMM:immediate)
*++e = ival; // 存储常量值(ival来自词法分析的数值)
next(); // 读取下一个token
ty = INT; // 标记当前表达式类型为整数(INT)
}
// 1.2 字符串常量(如"hello",词法分析已识别为")
else if (tk == '"') {
*++e = IMM; // 生成"立即数加载"指令
*++e = ival; // 存储字符串在数据区的起始地址(ival来自词法分析)
next(); // 跳过字符串起始引号
while (tk == '"') next(); // 跳过闭合引号
// 数据区地址按int大小对齐(内存对齐处理,避免未对齐访问)
data = (char *)((int)data + sizeof(int) & -sizeof(int));
ty = PTR; // 字符串本质是字符指针,标记类型为指针(PTR)
}
// 1.3 sizeof运算符(如sizeof(int)、sizeof(char*))
else if (tk == Sizeof) {
next(); // 跳过sizeof关键字
// 检查sizeof后是否有左括号
if (tk == '(') next();
else {
printf("%d: open paren expected in sizeof\n", line);
exit(-1);
}
ty = INT; // 默认类型为int
// 解析sizeof后的基础类型(int或char)
if (tk == Int) next(); // 处理sizeof(int)
else if (tk == Char) {
next();
ty = CHAR; // 处理sizeof(char)
}
// 处理指针类型(如sizeof(int*),多个*表示多级指针)
while (tk == Mul) {
next();
ty = ty + PTR; // 每多一个*,类型增加PTR(表示指针层级)
}
// 检查是否有右括号
if (tk == ')') next();
else {
printf("%d: close paren expected in sizeof\n", line);
exit(-1);
}
*++e = IMM; // 生成立即数指令
// 存储类型大小(char占1字节,int占4字节)
*++e = (ty == CHAR) ? sizeof(char) : sizeof(int);
ty = INT; // sizeof结果为整数,类型标记为INT
}
// 1.4 标识符(变量、函数、枚举常量等,词法分析已识别为Id)
else if (tk == Id) {
d = id; // 暂存当前标识符在符号表中的条目(id由词法分析设置)
next(); // 跳过标识符
// 1.4.1 函数调用(标识符后接'(',如printf(...))
if (tk == '(') {
next(); // 跳过左括号
t = 0; // 记录参数数量
// 解析函数参数(递归调用expr解析每个参数)
while (tk != ')') {
expr(Assign); // 解析参数表达式(优先级为赋值运算符级别)
*++e = PSH; // 生成参数压栈指令(PSH:push)
++t; // 参数计数+1
if (tk == ',') next(); // 跳过参数间的逗号
}
next(); // 跳过右括号
// 根据函数类型生成调用指令
if (d[Class] == Sys) {
// 系统函数(如printf)直接生成对应 opcode(如PRTF)
*++e = d[Val];
}
else if (d[Class] == Fun) {
// 用户函数生成跳转指令(JSR:jump to subroutine)
*++e = JSR;
*++e = d[Val]; // 函数入口地址(来自符号表)
}
else {
printf("%d: bad function call\n", line);
exit(-1);
}
// 若有参数,生成栈调整指令(ADJ:adjust,调整栈顶指针)
if (t) {
*++e = ADJ;
*++e = t; // 参数数量(用于恢复栈)
}
ty = d[Type]; // 函数返回值类型(来自符号表)
}
// 1.4.2 枚举常量(符号表中标记为Num类型)
else if (d[Class] == Num) {
*++e = IMM; // 生成立即数指令
*++e = d[Val]; // 存储枚举值(来自符号表)
ty = INT; // 枚举常量为整数类型
}
// 1.4.3 变量访问(局部变量或全局变量)
else {
if (d[Class] == Loc) {
// 局部变量:生成地址加载指令(LEA:load effective address)
// loc是当前栈帧基址偏移,d[Val]是变量在栈中的偏移,计算绝对地址
*++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);
}
// 加载变量值(LC:load char,加载字符;LI:load int,加载整数)
*++e = ((ty = d[Type]) == CHAR) ? LC : LI;
}
}
// 1.5 括号表达式或类型转换(如(a+b)、(int)c)
else if (tk == '(') {
next(); // 跳过左括号
// 1.5.1 类型转换(如(int)c、(char*)p)
if (tk == Int || tk == Char) {
t = (tk == Int) ? INT : CHAR; // 基础类型(int或char)
next(); // 跳过类型关键字
// 处理指针转换(如(int*)p,多个*表示多级指针)
while (tk == Mul) {
next();
t = t + PTR; // 每多一个*,类型增加PTR
}
// 检查是否有右括号
if (tk == ')') next();
else {
printf("%d: bad cast\n", line);
exit(-1);
}
expr(Inc); // 解析被转换的表达式(优先级为自增运算符级别)
ty = t; // 转换后的类型
}
// 1.5.2 括号表达式(如(a + b),改变运算优先级)
else {
expr(Assign); // 递归解析括号内的表达式(优先级为赋值级别)
// 检查是否有右括号
if (tk == ')') next();
else {
printf("%d: close paren expected\n", line);
exit(-1);
}
}
}
// 1.6 单目运算符:解引用(*p,取指针指向的值)
else if (tk == Mul) {
next(); // 跳过*
expr(Inc); // 解析指针表达式(优先级为自增级别)
// 解引用后类型为指针指向的基础类型(如int*解引用后为int)
if (ty > INT) ty = ty - PTR;
else {
printf("%d: bad dereference\n", line); // 非指针类型不能解引用
exit(-1);
}
// 加载解引用后的值(LC:字符,LI:整数)
*++e = (ty == CHAR) ? LC : LI;
}
// 1.7 单目运算符:取地址(&var,取变量的地址)
else if (tk == And) {
next(); // 跳过&
expr(Inc); // 解析变量表达式(优先级为自增级别)
// 移除值加载指令(LC/LI),保留地址(取地址操作的结果是地址)
if (*e == LC || *e == LI) --e;
else {
printf("%d: bad address-of\n", line); // 非左值不能取地址
exit(-1);
}
ty = ty + PTR; // 类型升级为指针(如int变量取地址后为int*)
}
// 1.8 单目运算符:逻辑非(!x,等价于x == 0)
else if (tk == '!') {
next(); // 跳过!
expr(Inc); // 解析表达式(优先级为自增级别)
*++e = PSH; // 压栈表达式结果
*++e = IMM; // 压栈立即数0
*++e = 0;
*++e = EQ; // 生成等于比较指令(EQ:equal,结果为1或0)
ty = INT; // 逻辑运算结果为整数
}
// 1.9 单目运算符:按位非(~x,等价于x ^ -1)
else if (tk == '~') {
next(); // 跳过~
expr(Inc); // 解析表达式(优先级为自增级别)
*++e = PSH; // 压栈表达式结果
*++e = IMM; // 压栈立即数-1(二进制全1)
*++e = -1;
*++e = XOR; // 生成异或指令(XOR:按位异或)
ty = INT; // 位运算结果为整数
}
// 1.10 单目运算符:正号(+x,仅语法支持,无实际运算)
else if (tk == Add) {
next(); // 跳过+
expr(Inc); // 解析表达式(优先级为自增级别)
ty = INT; // 结果类型为整数
}
// 1.11 单目运算符:负号(-x,等价于0 - x)
else if (tk == Sub) {
next(); // 跳过-
*++e = IMM; // 生成立即数指令
// 处理负号:若为数字直接取负,否则生成-1 * x
if (tk == Num) {
*++e = -ival; // 数字常量直接取负
next();
} else {
*++e = -1; // 非数字则生成-1
*++e = PSH; // 压栈-1
expr(Inc); // 解析表达式
*++e = MUL; // 生成乘法指令(-1 * x)
}
ty = INT; // 结果类型为整数
}
// 1.12 单目运算符:前缀自增/自减(++x / --x)
else if (tk == Inc || tk == Dec) {
t = tk; // 保存运算符类型(Inc/Dec)
next(); // 跳过++/--
expr(Inc); // 解析变量表达式(优先级为自增级别)
// 确保是左值(可修改的变量,通过LC/LI指令加载)
if (*e == LC) {
*e = PSH; // 将LC(加载字符)改为PSH(压栈)
*++e = LC; // 重新生成LC指令(先加载值再压栈)
}
else if (*e == LI) {
*e = PSH; // 将LI(加载整数)改为PSH(压栈)
*++e = LI; // 重新生成LI指令
}
else {
printf("%d: bad lvalue in pre-increment\n", line);
exit(-1);
}
*++e = PSH; // 压栈变量当前值
// 生成自增/自减的步长(int为4字节,char为1字节)
*++e = IMM;
*++e = (ty > PTR) ? sizeof(int) : sizeof(char);
// 生成加法/减法指令(自增用ADD,自减用SUB)
*++e = (t == Inc) ? ADD : SUB;
// 存储结果回变量(SC:存字符,SI:存整数)
*++e = (ty == CHAR) ? SC : SI;
}
// 未知表达式元素,报错
else {
printf("%d: bad expression\n", line);
exit(-1);
}
// 2. 优先级循环:基于"优先级攀升法"处理二元运算符
// 只处理优先级 >= lev 的运算符,确保运算顺序正确
while (tk >= lev) {
t = ty; // 保存当前表达式类型(用于后续运算的类型检查)
// 2.1 赋值运算符(=,如a = b + c)
if (tk == Assign) {
next(); // 跳过=
// 确保左值是可修改的变量(通过LC/LI加载)
if (*e == LC || *e == LI)
*e = PSH; // 将加载指令改为压栈指令(压栈左值地址)
else {
printf("%d: bad lvalue in assignment\n", line);
exit(-1);
}
expr(Assign); // 解析右值表达式(优先级为赋值级别)
// 存储结果到左值(SC:存字符,SI:存整数)
*++e = ((ty = t) == CHAR) ? SC : SI;
}
// 2.2 三目条件运算符(? :,如a ? b : c)
else if (tk == Cond) {
next(); // 跳过?
*++e = BZ; // 生成"为零跳转"指令(BZ:branch if zero)
d = ++e; // 暂存跳转地址(后续回填)
expr(Assign); // 解析"真"分支表达式
// 检查是否有冒号
if (tk == ':') next();
else {
printf("%d: conditional missing colon\n", line);
exit(-1);
}
*d = (int)(e + 3); // 回填假分支入口地址
*++e = JMP; // 生成无条件跳转指令(跳过真分支)
d = ++e; // 暂存跳转地址(后续回填)
expr(Cond); // 解析"假"分支表达式
*d = (int)(e + 1); // 回填分支结束地址
}
// 2.3 逻辑或(||,优先级低于条件运算符)
else if (tk == Lor) {
next(); // 跳过||
*++e = BNZ; // 生成"非零跳转"指令(BNZ:branch if not zero)
d = ++e; // 暂存跳转地址
expr(Lan); // 解析右表达式(优先级为逻辑与级别)
*d = (int)(e + 1); // 回填跳转地址
ty = INT; // 逻辑运算结果为整数
}
// 2.4 逻辑与(&&,优先级高于逻辑或)
else if (tk == Lan) {
next(); // 跳过&&
*++e = BZ; // 生成"为零跳转"指令
d = ++e; // 暂存跳转地址
expr(Or); // 解析右表达式(优先级为按位或级别)
*d = (int)(e + 1); // 回填跳转地址
ty = INT; // 逻辑运算结果为整数
}
// 2.5 按位或(|,优先级高于逻辑与)
else if (tk == Or) {
next(); // 跳过|
*++e = PSH; // 压栈左表达式结果
expr(Xor); // 解析右表达式(优先级为按位异或级别)
*++e = OR; // 生成按位或指令
ty = INT; // 位运算结果为整数
}
// 2.6 按位异或(^,优先级高于按位或)
else if (tk == Xor) {
next(); // 跳过^
*++e = PSH; // 压栈左表达式结果
expr(And); // 解析右表达式(优先级为按位与级别)
*++e = XOR; // 生成按位异或指令
ty = INT; // 位运算结果为整数
}
// 2.7 按位与(&,优先级高于按位异或)
else if (tk == And) {
next(); // 跳过&
*++e = PSH; // 压栈左表达式结果
expr(Eq); // 解析右表达式(优先级为比较运算符级别)
*++e = AND; // 生成按位与指令
ty = INT; // 位运算结果为整数
}
// 2.8 等于(==,优先级高于按位与)
else if (tk == Eq) {
next(); // 跳过==
*++e = PSH; // 压栈左表达式结果
expr(Lt); // 解析右表达式(优先级为小于/大于级别)
*++e = EQ; // 生成等于比较指令
ty = INT; // 比较结果为整数(1真/0假)
}
// 2.9 不等于(!=,优先级高于按位与)
else if (tk == Ne) {
next(); // 跳过!=
*++e = PSH; // 压栈左表达式结果
expr(Lt); // 解析右表达式(优先级为小于/大于级别)
*++e = NE; // 生成不等于比较指令
ty = INT; // 比较结果为整数
}
// 2.10 小于(<,优先级高于等于/不等于)
else if (tk == Lt) {
next(); // 跳过<
*++e = PSH; // 压栈左表达式结果
expr(Shl); // 解析右表达式(优先级为移位级别)
*++e = LT; // 生成小于比较指令
ty = INT; // 比较结果为整数
}
// 2.11 大于(>,优先级高于等于/不等于)
else if (tk == Gt) {
next(); // 跳过>
*++e = PSH; // 压栈左表达式结果
expr(Shl); // 解析右表达式(优先级为移位级别)
*++e = GT; // 生成大于比较指令
ty = INT; // 比较结果为整数
}
// 2.12 小于等于(<=,优先级高于等于/不等于)
else if (tk == Le) {
next(); // 跳过<=
*++e = PSH; // 压栈左表达式结果
expr(Shl); // 解析右表达式(优先级为移位级别)
*++e = LE; // 生成小于等于比较指令
ty = INT; // 比较结果为整数
}
// 2.13 大于等于(>=,优先级高于等于/不等于)
else if (tk == Ge) {
next(); // 跳过>=
*++e = PSH; // 压栈左表达式结果
expr(Shl); // 解析右表达式(优先级为移位级别)
*++e = GE; // 生成大于等于比较指令
ty = INT; // 比较结果为整数
}
// 2.14 左移(<<,优先级高于比较运算符)
else if (tk == Shl) {
next(); // 跳过<<
*++e = PSH; // 压栈左表达式结果
expr(Add); // 解析右表达式(优先级为加减级别)
*++e = SHL; // 生成左移指令
ty = INT; // 移位结果为整数
}
// 2.15 右移(>>,优先级高于比较运算符)
else if (tk == Shr) {
next(); // 跳过>>
*++e = PSH; // 压栈左表达式结果
expr(Add); // 解析右表达式(优先级为加减级别)
*++e = SHR; // 生成右移指令
ty = INT; // 移位结果为整数
}
// 2.16 加法(+,优先级高于移位)
else if (tk == Add) {
next(); // 跳过+
*++e = PSH; // 压栈左表达式结果
expr(Mul); // 解析右表达式(优先级为乘除级别)
// 处理指针加法(如p + n,需计算n * sizeof(*p))
if ((ty = t) > PTR) {
*++e = PSH; // 压栈右表达式结果(n)
*++e = IMM; // 压栈元素大小(sizeof(int))
*++e = sizeof(int);
*++e = MUL; // 计算n * sizeof(int)
}
*++e = ADD; // 生成加法指令
}
// 2.17 减法(-,优先级高于移位)
else if (tk == Sub) {
next(); // 跳过-
*++e = PSH; // 压栈左表达式结果
expr(Mul); // 解析右表达式(优先级为乘除级别)
// 处理指针减法(如p - q,结果为(p - q)/sizeof(*p))
if (t > PTR && t == ty) {
*++e = SUB; // 计算地址差(p - q)
*++e = PSH; // 压栈结果
*++e = IMM; // 压栈元素大小(sizeof(int))
*++e = sizeof(int);
*++e = DIV; // 计算地址差 / sizeof(int)
ty = INT; // 结果为整数(元素个数)
}
// 处理指针减整数(如p - n,需计算n * sizeof(*p))
else if ((ty = t) > PTR) {
*++e = PSH; // 压栈右表达式结果(n)
*++e = IMM; // 压栈元素大小
*++e = sizeof(int);
*++e = MUL; // 计算n * sizeof(int)
*++e = SUB; // 计算p - (n * sizeof(int))
}
// 普通整数减法
else *++e = SUB;
}
// 2.18 乘法(*,优先级高于加减)
else if (tk == Mul) {
next(); // 跳过*
*++e = PSH; // 压栈左表达式结果
expr(Inc); // 解析右表达式(优先级为自增级别)
*++e = MUL; // 生成乘法指令
ty = INT; // 乘法结果为整数
}
// 2.19 除法(/,优先级高于加减)
else if (tk == Div) {
next(); // 跳过/
*++e = PSH; // 压栈左表达式结果
expr(Inc); // 解析右表达式(优先级为自增级别)
*++e = DIV; // 生成除法指令
ty = INT; // 除法结果为整数
}
// 2.20 取模(%,优先级高于加减)
else if (tk == Mod) {
next(); // 跳过%
*++e = PSH; // 压栈左表达式结果
expr(Inc); // 解析右表达式(优先级为自增级别)
*++e = MOD; // 生成取模指令
ty = INT; // 取模结果为整数
}
// 2.21 后缀自增/自减(x++ / x--)
else if (tk == Inc || tk == Dec) {
// 确保是左值(可修改的变量)
if (*e == LC) {
*e = PSH; // 将LC改为PSH(压栈地址)
*++e = LC; // 重新生成LC(加载当前值)
}
else if (*e == LI) {
*e = PSH; // 将LI改为PSH(压栈地址)
*++e = LI; // 重新生成LI(加载当前值)
}
else {
printf("%d: bad lvalue in post-increment\n", line);
exit(-1);
}
*++e = PSH; // 压栈当前值(用于返回原始值)
// 生成步长(int为4字节,char为1字节)
*++e = IMM;
*++e = (ty > PTR) ? sizeof(int) : sizeof(char);
// 生成加法/减法指令(自增用ADD,自减用SUB)
*++e = (tk == Inc) ? ADD : SUB;
// 存储结果回变量(SC:存字符,SI:存整数)
*++e = (ty == CHAR) ? SC : SI;
// 调整返回值为原始值(减去/加上步长)
*++e = PSH;
*++e = IMM;
*++e = (ty > PTR) ? sizeof(int) : sizeof(char);
*++e = (tk == Inc) ? SUB : ADD;
next(); // 跳过++/--
}
// 2.22 数组访问([],如arr[i])
else if (tk == Brak) {
next(); // 跳过[
*++e = PSH; // 压栈数组地址(左表达式结果)
expr(Assign); // 解析索引表达式(优先级为赋值级别)
// 检查是否有右括号
if (tk == ']') next();
else {
printf("%d: close bracket expected\n", line);
exit(-1);
}
// 处理指针类型的数组(计算索引偏移:i * sizeof(element))
if (t > PTR) {
*++e = PSH; // 压栈索引值
*++e = IMM; // 压栈元素大小(sizeof(int))
*++e = sizeof(int);
*++e = MUL; // 计算i * sizeof(int)
}
// 非指针类型不能作为数组访问
else if (t < PTR) {
printf("%d: pointer type expected\n", line);
exit(-1);
}
*++e = ADD; // 计算元素地址(数组地址 + 偏移量)
// 加载元素值(类型为数组元素类型:CHAR或INT)
*++e = ((ty = t - PTR) == CHAR) ? LC : LI;
}
// 未知运算符,报错
else {
printf("%d: compiler error tk=%d\n", line, tk);
exit(-1);
}
}
}
stmt() 函数
核心内容
stmt 函数是语句解析器,处理 C 语言中的基本语句(条件、循环、返回、复合语句等),生成对应的控制流中间代码。支持的语句包括:
- 条件语句(
If):解析if (expr) stmt [else stmt],生成条件跳转(BZ)和无条件跳转(JMP)指令,通过地址回填实现分支逻辑。 - 循环语句(
While):解析while (expr) stmt,生成循环条件判断(BZ)和跳转回循环开头的指令(JMP)。 - 返回语句(
Return):解析return [expr];,生成函数退出指令(LEV)。 - 复合语句(
{}):递归解析花括号内的所有语句。 - 空语句(
;):直接跳过。 - 表达式语句:解析表达式后处理分号(
;),生成表达式对应的中间代码。
关键变量
a/b:指针,用于记录分支跳转指令的地址(如if的else分支入口、while的循环开头),用于后续地址回填。tk:当前词法单元,决定当前解析的语句类型(如If/While/Return等)。e:指向当前生成的中间代码的指针,生成控制流指令(BZ/JMP/LEV等)。
// 解析并生成语句对应的中间代码,处理条件、循环、返回、复合语句等
void stmt()
{
int *a, *b; // 用于记录跳转指令地址,后续进行回填
// 处理 if 语句:if (expr) stmt [else stmt]
if (tk == If) {
next(); // 跳过"If"关键字
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; // 生成条件跳转指令(若为0则跳转)
b = ++e; // 暂存跳转目标地址(待回填)
stmt(); // 解析if的主体语句
// 处理else分支
if (tk == Else) {
*b = (int)(e + 3); // 回填BZ的目标地址(跳转到else分支之后)
*++e = JMP; // 生成无条件跳转指令(跳过else分支)
b = ++e; // 暂存跳转目标地址(待回填)
next(); // 跳过"Else"关键字
stmt(); // 解析else的主体语句
}
*b = (int)(e + 1); // 回填跳转目标地址(指向整个if-else结构之后)
}
// 处理 while 循环:while (expr) stmt
else if (tk == While) {
next(); // 跳过"While"关键字
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; // 生成条件跳转指令(若条件为0则跳出循环)
b = ++e; // 暂存跳转目标地址(待回填)
stmt(); // 解析循环体语句
*++e = JMP; // 生成无条件跳转指令(跳回循环条件判断)
*++e = (int)a; // 跳转目标为循环条件起始地址
*b = (int)(e + 1); // 回填BZ的目标地址(指向循环结束后)
}
// 处理 return 语句:return [expr];
else if (tk == Return) {
next(); // 跳过"Return"关键字
if (tk != ';') expr(Assign); // 若有返回值,解析表达式
*++e = LEV; // 生成函数退出指令(恢复栈帧并返回)
if (tk == ';') next(); // 跳过分号';'
else { printf("%d: semicolon expected\n", line); exit(-1); } // 语法错误:缺少';'
}
// 处理复合语句(花括号包裹的语句块):{ stmt* }
else if (tk == '{') {
next(); // 跳过左花括号'{'
while (tk != '}') stmt(); // 递归解析内部所有语句
next(); // 跳过右花括号'}'
}
// 处理空语句:;
else if (tk == ';') {
next(); // 直接跳过分号
}
// 处理表达式语句:expr;
else {
expr(Assign); // 解析表达式(优先级为赋值运算符级别)
if (tk == ';') next(); // 跳过分号';'
else { printf("%d: semicolon expected\n", line); exit(-1); } // 语法错误:缺少';'
}
}
main() 函数
核心内容
main 函数是程序入口,整合了编译器的全流程:命令行参数处理、内存分配、符号表初始化、词法 / 语法分析(解析源代码生成中间代码)、虚拟机执行中间代码。具体流程:
- 参数处理:解析
-s(输出源码和汇编)、-d(调试模式)选项,读取输入源文件。 - 内存分配:为符号表、中间代码、数据区、栈区分配内存。
- 符号表初始化:预定义关键字(
int/char等)和系统函数(printf/malloc等)到符号表。 - 解析源代码:读取源文件,通过词法分析(
next())和语法分析(stmt()/expr())解析声明(全局变量、函数)和语句,生成中间代码。 - 虚拟机执行:初始化栈和寄存器,解释执行生成的中间代码(通过模拟指令集实现运算、跳转、系统调用等)。
关键变量
argc/argv:命令行参数,用于获取输入文件和选项(-s/-d)。src/debug:标志位,src控制是否输出源码和汇编,debug控制是否打印执行的指令。sym/e/data/sp:分别指向符号表、中间代码区、数据区、栈区的指针,管理内存分配。idmain:指向main函数在符号表中的条目,用于获取其入口地址。pc/sp/bp:虚拟机寄存器,pc为程序计数器(指向当前执行的指令),sp为栈指针,bp为基址指针(用于函数调用栈帧)。a:虚拟机中的累加器,存储当前运算结果或指令操作数。
// 程序入口,负责命令行解析、内存分配、词法/语法分析、中间代码生成和虚拟机执行
int main(int argc, char **argv)
{
int fd, bt, ty, poolsz, *idmain; // fd:文件描述符;bt:基础类型;ty:类型;poolsz:内存池大小;idmain:main函数符号表条目
int *pc, *sp, *bp, a, cycle; // 虚拟机寄存器:pc程序计数器;sp栈指针;bp基址指针;a累加器;cycle指令执行计数
int i, *t; // 临时变量
// 解析命令行参数(-s:输出源码和汇编;-d:调试模式)
--argc; ++argv;
if (argc > 0 &&**argv == '-' && (*argv)[1] == 's') { src = 1; --argc; ++argv; }
if (argc > 0 && **argv == '-' && (*argv)[1] == 'd') { debug = 1; --argc; ++argv; }
if (argc < 1) { printf("usage: c4 [-s] [-d] file ...\n"); return -1; } // 参数错误
// 打开输入源文件
if ((fd = open(*argv, 0)) < 0) { printf("could not open(%s)\n", *argv); return -1; }
// 分配内存池(符号表、中间代码、数据区、栈区)
poolsz = 256*1024; // 内存池大小(固定值,可调整)
if (!(sym = malloc(poolsz))) { printf("could not malloc(%d) symbol area\n", poolsz); return -1; }
if (!(le = e = malloc(poolsz))) { printf("could not malloc(%d) text area\n", poolsz); return -1; }
if (!(data = malloc(poolsz))) { printf("could not malloc(%d) data area\n", poolsz); return -1; }
if (!(sp = malloc(poolsz))) { printf("could not malloc(%d) stack area\n", poolsz); return -1; }
// 初始化内存池(清零)
memset(sym, 0, poolsz);
memset(e, 0, poolsz);
memset(data, 0, poolsz);
// 初始化符号表:添加关键字和系统函数
p = "char else enum if int return sizeof while "
"open read close printf malloc free memset memcmp exit void main";
i = Char; while (i <= While) { next(); id[Tk] = i++; } // 添加关键字(Char到While)
i = OPEN; while (i <= EXIT) { next(); id[Class] = Sys; id[Type] = INT; id[Val] = i++; } // 添加系统函数(OPEN到EXIT)
next(); id[Tk] = Char; // 处理void类型(映射为Char)
next(); idmain = id; // 记录main函数的符号表条目
// 读取源文件内容到内存
if (!(lp = p = malloc(poolsz))) { printf("could not malloc(%d) source area\n", poolsz); return -1; }
if ((i = read(fd, p, poolsz-1)) <= 0) { printf("read() returned %d\n", i); return -1; }
p[i] = 0; // 添加字符串结束符
close(fd); // 关闭文件
// 解析全局声明和函数定义
line = 1; // 初始化行号
next(); // 读取第一个token
while (tk) { // 循环解析直到文件结束
bt = INT; // 默认基础类型为int
if (tk == Int) next(); // 处理int类型声明
else if (tk == Char) { next(); bt = CHAR; } // 处理char类型声明
else if (tk == Enum) { // 处理枚举类型
next();
if (tk != '{') next(); // 跳过枚举名(若有)
if (tk == '{') { // 解析枚举值列表
next();
i = 0; // 枚举值计数器(默认从0开始)
while (tk != '}') {
if (tk != Id) { printf("%d: bad enum identifier %d\n", line, tk); return -1; }
next();
if (tk == Assign) { // 处理显式赋值(如enum {A=5, B})
next();
if (tk != Num) { printf("%d: bad enum initializer\n", line); return -1; }
i = ival; // 更新枚举值为指定值
next();
}
id[Class] = Num; id[Type] = INT; id[Val] = i++; // 记录枚举常量到符号表
if (tk == ',') next(); // 跳过逗号
}
next(); // 跳过右花括号'}'
}
}
// 解析声明列表(变量或函数),直到遇到分号或右花括号
while (tk != ';' && tk != '}') {
ty = bt; // 初始化类型为基础类型
while (tk == Mul) { next(); ty = ty + PTR; } // 处理指针(如int** -> 类型为INT+PTR+PTR)
if (tk != Id) { printf("%d: bad global declaration\n", line); return -1; } // 语法错误:缺少标识符
if (id[Class]) { printf("%d: duplicate global definition\n", line); return -1; } // 语义错误:重复定义
next(); // 跳过标识符
id[Type] = ty; // 记录类型到符号表
// 处理函数定义(标识符后接'(')
if (tk == '(') {
id[Class] = Fun; // 标记为函数
id[Val] = (int)(e + 1); // 记录函数入口地址(中间代码位置)
next(); // 跳过左括号'('
i = 0; // 参数计数器
// 解析函数参数列表
while (tk != ')') {
ty = INT; // 参数默认类型为int
if (tk == Int) next();
else if (tk == Char) { next(); ty = CHAR; }
while (tk == Mul) { next(); ty = ty + PTR; } // 处理指针参数
if (tk != Id) { printf("%d: bad parameter declaration\n", line); return -1; } // 语法错误:参数缺少标识符
if (id[Class] == Loc) { printf("%d: duplicate parameter definition\n", line); return -1; } // 语义错误:参数重复
// 保存原有符号表信息(后续恢复,避免污染全局符号表)
id[HClass] = id[Class]; id[Class] = Loc;
id[HType] = id[Type]; id[Type] = ty;
id[HVal] = id[Val]; id[Val] = i++; // 参数在栈帧中的偏移
next(); // 跳过参数名
if (tk == ',') next(); // 跳过逗号
}
next(); // 跳过右括号')'
if (tk != '{') { printf("%d: bad function definition\n", line); return -1; } // 语法错误:函数体缺少'{'
loc = ++i; // 局部变量起始偏移(i为参数个数,loc = 参数个数 + 1)
next(); // 跳过左花括号'{'
// 解析函数内的局部变量声明
while (tk == Int || tk == Char) {
bt = (tk == Int) ? INT : CHAR; // 局部变量基础类型
next();
while (tk != ';') { // 处理同类型的多个变量声明(如int a, b;)
ty = bt;
while (tk == Mul) { next(); ty = ty + PTR; } // 处理指针
if (tk != Id) { printf("%d: bad local declaration\n", line); return -1; } // 语法错误:缺少标识符
if (id[Class] == Loc) { printf("%d: duplicate local definition\n", line); return -1; } // 语义错误:局部变量重复
// 保存原有符号表信息(后续恢复)
id[HClass] = id[Class]; id[Class] = Loc;
id[HType] = id[Type]; id[Type] = ty;
id[HVal] = id[Val]; id[Val] = ++i; // 局部变量在栈帧中的偏移
next(); // 跳过变量名
if (tk == ',') next(); // 跳过逗号
}
next(); // 跳过分号';'
}
// 生成函数入口指令(ENT:创建栈帧)
*++e = ENT; *++e = i - loc; // i - loc 为局部变量个数
// 解析函数体语句
while (tk != '}') stmt();
// 生成函数退出指令(LEV:销毁栈帧并返回)
*++e = LEV;
// 恢复符号表(清除局部变量标记,恢复全局信息)
id = sym;
while (id[Tk]) {
if (id[Class] == Loc) {
id[Class] = id[HClass];
id[Type] = id[HType];
id[Val] = id[HVal];
}
id = id + Idsz; // 移动到下一个符号表条目
}
}
// 处理全局变量定义(非函数)
else {
id[Class] = Glo; // 标记为全局变量
id[Val] = (int)data; // 记录变量在数据区的地址
data = data + sizeof(int); // 数据区指针后移(按int对齐)
}
if (tk == ',') next(); // 跳过逗号(处理多个声明)
}
next(); // 跳过分号或右花括号
}
// 检查main函数是否定义
if (!(pc = (int *)idmain[Val])) { printf("main() not defined\n"); return -1; }
if (src) return 0; // 若指定-s选项,仅输出源码和汇编,不执行
// 初始化虚拟机栈
bp = sp = (int *)((int)sp + poolsz); // 栈指针从高地址开始(向下增长)
*--sp = EXIT; // 栈底压入EXIT指令(main返回后执行)
*--sp = PSH; t = sp; // 构造参数列表(模拟argc和argv)
*--sp = argc;
*--sp = (int)argv;
*--sp = (int)t;
// 虚拟机执行中间代码
cycle = 0; // 指令执行计数器
while (1) {
i = *pc++; ++cycle; // 取当前指令,pc自增,计数器加1
// 调试模式:打印当前执行的指令
if (debug) {
printf("%d> %.4s", cycle,
&"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,"[i * 5]);
if (i <= ADJ) printf(" %d\n", *pc); else printf("\n");
}
// 执行指令(根据 opcode 处理)
if (i == LEA) a = (int)(bp + *pc++); // 加载局部变量地址(bp + 偏移)
else if (i == IMM) a = *pc++; // 加载立即数或全局变量地址
else if (i == JMP) pc = (int *)*pc; // 无条件跳转
else if (i == JSR) { *--sp = (int)(pc + 1); pc = (int *)*pc; } // 调用子函数(保存返回地址)
else if (i == BZ) pc = a ? pc + 1 : (int *)*pc; // 为零则跳转
else if (i == BNZ) pc = a ? (int *)*pc : pc + 1; // 非零则跳转
else if (i == ENT) { *--sp = (int)bp; bp = sp; sp = sp - *pc++; } // 进入函数(创建栈帧)
else if (i == ADJ) sp = sp + *pc++; // 调整栈(丢弃参数)
else if (i == LEV) { sp = bp; bp = (int *)*sp++; pc = (int *)*sp++; } // 离开函数(恢复栈帧)
else if (i == LI) a = *(int *)a; // 加载int值(从地址a)
else if (i == LC) a = *(char *)a; // 加载char值(从地址a)
else if (i == SI) *(int *)*sp++ = a; // 存储int值(到栈顶地址)
else if (i == SC) a = *(char *)*sp++ = a; // 存储char值(到栈顶地址)
else if (i == PSH) *--sp = a; // 压栈(将a存入栈顶)
// 算术/逻辑运算指令
else if (i == OR) a = *sp++ | a;
else if (i == XOR) a = *sp++ ^ a;
else if (i == AND) a = *sp++ & a;
else if (i == EQ) a = *sp++ == a;
else if (i == NE) a = *sp++ != a;
else if (i == LT) a = *sp++ < a;
else if (i == GT) a = *sp++ > a;
else if (i == LE) a = *sp++ <= a;
else if (i == GE) a = *sp++ >= a;
else if (i == SHL) a = *sp++ << a;
else if (i == SHR) a = *sp++ >> a;
else if (i == ADD) a = *sp++ + a;
else if (i == SUB) a = *sp++ - a;
else if (i == MUL) a = *sp++ * a;
else if (i == DIV) a = *sp++ / a;
else if (i == MOD) a = *sp++ % a;
// 系统调用指令(映射到操作系统API)
else if (i == OPEN) a = open((char *)sp[1], *sp);
else if (i == READ) a = read(sp[2], (char *)sp[1], *sp);
else if (i == CLOS) a = close(*sp);
else if (i == PRTF) { t = sp + pc[1]; a = printf((char *)t[-1], t[-2], t[-3], t[-4], t[-5], t[-6]); }
else if (i == MALC) a = (int)malloc(*sp);
else if (i == FREE) free((void *)*sp);
else if (i == MSET) a = (int)memset((char *)sp[2], sp[1], *sp);
else if (i == MCMP) a = memcmp((char *)sp[2], (char *)sp[1], *sp);
else if (i == EXIT) { printf("exit(%d) cycle = %d\n", *sp, cycle); return *sp; } // 程序退出
else { printf("unknown instruction = %d! cycle = %d\n", i, cycle); return -1; } // 未知指令错误
}
}
编译器自举的通俗理解
编辑器自举的精髓是“从简单到复杂,用已有的工具构建更强大的工具”。
- 起点:汇编写的“迷你编译器”
一开始没有C编译器,人们先用汇编语言(直接操作机器指令的低级语言)写了一个功能极简的编译器——它只能处理C语言里最核心的部分(比如变量定义、简单函数调用、基本运算),够用来编译一个“用C写的编译器源码”就行。
这一步就像“用石头造出一把最原始的刀”,只能切简单的东西,但够用了。 - 第一次迭代:用迷你编译器编译C版编译器
有人用C语言写了一个更完善的编译器源码(假设叫 c_compiler.c ),这个源码里已经包含了更复杂的语法处理逻辑(比如循环、指针、结构体等)。
虽然这个 c_compiler.c 很复杂,但它的“核心部分”(变量、简单函数)是能被第一步的迷你编译器识别的。于是用迷你编译器去编译 c_compiler.c ,得到一个新的可执行文件(比如 cc1 )——这个 cc1 就是“用C语言编译出来的编译器”了,它的功能比迷你编译器强很多。
这一步相当于“用原始的刀打磨出一把更锋利的刀”,能切更复杂的东西了。 - 自举完成:用新编译器编译自己(
现在有了 cc1 ,它已经能处理大部分C语法了。这时候再用 cc1 去编译 c_compiler.c 本身,得到的新编译器(比如 cc2 )功能会更完善(因为 cc1 比迷你编译器强,能识别 c_compiler.c 里更复杂的逻辑)。 - 重复几次后,最终得到的编译器(比如 gcc )已经能完美处理所有C语法,而且它完全是用C语言编译出来的可执行文件——这就是“自举成功”。
就像“用锋利的刀造出了一把更完美的刀”,之后这把刀就能独立切割任何东西(编译其他C文件)了。
简单来说,编译器自举就是一个 “逐步解锁高级功能” 的过程,核心逻辑可以总结为:
- 源代码包含所有目标功能(比如 A、B、C、D、E 等语法的解析逻辑),但这些功能的实现是 “分层” 的;
- 早期编译器只能处理基础层(比如 A、B、C),用它编译源代码时,只能将 “基础层功能 + 依赖基础层实现的 D” 编译出来,生成支持 A、B、C、D 的新编译器(此时虽然源代码支持E但生成的编译器是不支持的);
- 新编译器能力更强(支持 D),此时再用它编译同一套源代码,就能处理 “依赖 D 实现的 E”,生成支持 A、B、C、D、E 的更强编译器;
- 以此类推,每一轮自举都能 “解锁” 源代码中依赖上一层功能实现的新特性,最终让编译器支持所有语法。
这个过程就像搭积木:
- 先用小积木(基础语法)搭出能抓更大积木的 “机械手”(支持 D 的编译器);
- 再用这个机械手抓更大的积木(依赖 D 的 E),搭出更强的 “机械手”(支持 E 的编译器);
- 直到所有积木都能被抓取(所有语法都被支持)。
每一步的关键是:“实现高级功能的代码,本身是用更低级的功能写的”,因此总能被上一代编译器处理。这就是自举能 “从弱到强” 的核心逻辑。
更多推荐
所有评论(0)