写之前的小引子+思考:C语言的彼岸与更深层的召唤
如果你已经扎实地掌握了C语言,那么可以发现教会了我们内存管理、指针操作、数据结构和算法的基础,让我们得以窥见计算机内部运作的一角。然而,在程序员的旅途中,总有一些“传说”萦绕耳畔:比C语言更难、更底层、更晦涩的“汇编语言”;以及将高级语言魔法般转化为机器指令的“编译原理”。它们仿佛是通往计算机核心奥秘的最后两扇大门,令人既敬畏又好奇。
许多C语言开发者,在面对性能瓶颈、系统底层问题或逆向工程时,常常会感到C语言的抽象层次似乎不够用
那么,这些“传说中”的知识究竟是什么?它们为何被认为是“更底层”的存在?掌握它们又能为编程生涯带来怎样的蜕变?将以“深入浅出”的方式,循序渐进地揭开汇编语言的神秘面纱,剖析编译器的魔法,从根源上理解程序是如何在计算机中运行的。
本第一部分将作为引子,奠定坚实的基础:
-
审视C语言的抽象层次: 为什么C语言虽然强大,但在某些场景下,我们仍然需要更底层的视角?
-
初识汇编语言: 汇编语言究竟是什么?它与机器码有何关系?为什么我们还需要学习它?
-
汇编语言的核心概念与简单示例: 介绍寄存器、内存、指令等基本构成,并通过具体的x86-64汇编代码片段,直观展示C语言的简单操作如何在汇编层面被执行。
-
编译原理的宏观视角: 编译器究竟做了哪些工作,才能将C代码转换为汇编代码?
1. 为什么C语言“不够底层”?理解抽象层级
C语言常被誉为“面向硬件的高级语言”,因为它提供了直接访问内存地址的能力(通过指针),允许程序员对程序执行流程进行细粒度控制。这使得C语言在开发操作系统内核、设备驱动、嵌入式系统以及性能敏感型应用方面具有无可比拟的优势。
然而,尽管C语言“贴近硬件”,但它终究是一种高级语言。这意味着它仍然存在一层抽象,将我们从机器的原始指令和物理内存地址中解放出来。
1.1 C语言的抽象层级:便利与限制
C语言提供的抽象主要体现在以下几个方面:
-
变量与数据类型: 我们使用
int
,float
,char
等类型来声明变量,而无需关心这些数据在内存中是如何具体存储的(例如,一个int
占用多少字节,这些字节的排列顺序如何)。C语言负责将这些抽象类型映射到机器的位和字节。 -
控制结构: 我们使用
if-else
,for
,while
等语句来控制程序的流程,而无需手动编写条件跳转(jmp
,je
,jne
)指令。 -
函数: 我们通过函数来组织代码,实现模块化和代码重用,而无需手动管理函数调用时的栈帧(参数传递、局部变量分配、返回地址保存等)。
-
数组与指针: C语言的指针虽然强大,但它仍然是基于虚拟内存地址的抽象,而非物理内存地址。数组提供了连续内存块的抽象,使得我们可以通过索引方便地访问元素。
-
标准库: 像
printf
,scanf
,malloc
,free
等标准库函数,更是封装了大量底层操作系统服务,让我们能够方便地进行输入输出和内存管理,而无需直接与操作系统的系统调用接口打交道。
这种抽象带来了巨大的便利:它提高了开发效率,使得程序更易读、易写、易维护、易移植。我们不必事无巨细地考虑每个操作对应的机器指令,也无需记住复杂的机器码序列。
然而,这种抽象也意味着在某些情况下,我们对程序在硬件层面如何运行的掌控力有所限制:
-
极致性能优化: 在追求纳秒级性能的场景(如游戏引擎、高性能计算、实时系统),C语言编译器生成的代码可能不是最优的。了解汇编可以帮助我们识别编译器瓶颈,甚至手动编写关键代码片段的汇编版本,以榨取硬件的最后一丝性能。
-
底层系统开发: 操作系统内核、设备驱动、引导加载程序等,需要直接与CPU寄存器、内存控制器、中断控制器等硬件交互。C语言虽然可以嵌入汇编代码,但深入理解这些系统的运行,仍离不开汇编语言的知识。
-
逆向工程与安全分析: 分析恶意软件、评估程序漏洞,往往需要直接分析可执行文件的机器码或汇编代码,因为原始C代码通常不可用。
-
调试复杂问题: 当C代码出现段错误(Segmentation Fault)、非法内存访问、诡异的运行时行为时,通过查看程序的汇编代码和寄存器状态,可以更精确地定位问题根源,理解程序崩溃的真实原因。
-
理解计算机体系结构: 汇编语言是理解CPU工作原理、指令集架构(ISA)、内存层次结构(缓存)、操作系统调度等底层概念的桥梁。
1.2 编译器的魔力:C代码如何变成机器码的初步印象
我们撰写的C代码,之所以能在计算机上运行,完全得益于编译器的“魔法”。编译器扮演着翻译官的角色,将我们用C语言表达的逻辑,翻译成CPU能够直接理解和执行的机器码(Machine Code)。
机器码是计算机硬件唯一能够直接执行的指令集合。它由一系列二进制数字(0和1)组成,每条指令对应CPU的一个基本操作,例如:将一个值从内存加载到寄存器,执行加法运算,或根据条件跳转到程序的另一部分。
一个简单的C语言赋值语句 a = b + 10;
在经过编译后,可能被翻译成多条机器指令。这些指令会执行以下步骤:
-
将变量
b
的值从内存加载到一个CPU寄存器中。 -
将常量
10
加载到另一个寄存器中,或直接与前一个寄存器的值相加。 -
将计算结果存回到变量
a
对应的内存地址。
而汇编语言,正是机器码的一种助记符表示。它用人类可读的符号(如 MOV
, ADD
, JMP
)来代替冗长的二进制机器码,使得程序员能够直接编写和阅读与机器指令一一对应的程序。
2. 揭开汇编语言的神秘面纱:机器的直接对话
汇编语言是介于高级语言和机器语言之间的一种低级语言。它是对机器语言的符号化表示,用助记符(Mnemonics)代替二进制指令码,用符号地址代替数值地址,大大提高了程序的可读性。
2.1 什么是汇编语言?与机器码的关系
-
机器语言: 计算机硬件唯一能理解的语言。它是CPU指令集的二进制编码。例如,
00000000010010000000000110101011
可能代表一条“将寄存器A的值加到寄存器B”的指令。 -
汇编语言: 机器语言的符号化表示。每一条汇编指令通常对应一条机器指令(一对一映射)。它使用易于记忆的助记符来代表操作码,使用符号(如变量名、标签)来代表内存地址和常量。
-
例如,上面的机器指令可能对应汇编指令
ADD AX, BX
(将BX寄存器的值加到AX寄存器)。
-
-
汇编器(Assembler): 负责将汇编语言代码翻译成机器语言代码的程序。这个过程称为汇编(Assembly)。
-
反汇编器(Disassembler): 负责将机器语言代码翻译回汇编语言代码的程序。
不同架构的汇编:
需要强调的是,汇编语言是与CPU架构紧密相关的。不同的CPU架构有不同的指令集,因此它们的汇编语言也各不相同。本系列将主要以目前最常用的x86-64架构(Intel/AMD桌面和服务器处理器)为例进行讲解,因为它在个人电脑和服务器领域占据主导地位,且其汇编指令较为丰富,足以展示底层编程的复杂性和精妙之处。当然,还有ARM架构(智能手机、平板电脑、树莓派等)、RISC-V等其他重要架构,它们有各自独特的指令集和汇编语法。
2.2 为什么学习汇编?它的重要性
学习汇编语言并非为了日常编程,而是为了获得更深层次的理解和解决特定问题的能力。它的重要性体现在:
-
理解程序执行的本质: 汇编语言直接暴露了CPU如何执行指令、如何管理数据、如何进行控制流跳转等底层机制。这有助于我们理解C语言乃至其他高级语言的运行时行为,例如:
-
函数调用机制: 参数如何传递、局部变量如何分配、返回地址如何保存和恢复。
-
内存访问: 变量在内存中的布局、指针的解引用如何转化为内存地址的读写。
-
条件判断与循环:
if/else
,for/while
如何被翻译成条件跳转指令。
-
-
性能优化:
-
识别瓶颈: 通过查看编译器生成的汇编代码,可以发现编译器在某些场景下未能充分利用CPU特性或生成了低效的代码。
-
手动优化: 在极少数对性能要求严苛的关键代码路径(如加密算法、图像处理核心算法),程序员可以手动编写汇编代码来替代C代码,以实现次量级的性能提升。
-
理解指令管道与缓存: 汇编代码的编写和组织方式会影响CPU的指令管道填充和缓存命中率,从而影响程序性能。
-
-
底层系统开发:
-
操作系统内核: 操作系统内核的启动、中断处理、上下文切换等核心功能,往往需要用汇编语言编写。
-
设备驱动: 直接与硬件寄存器交互的部分可能需要汇编。
-
引导加载程序(Bootloader): 计算机启动时执行的第一段代码,通常是纯汇编或嵌入汇编的C代码。
-
-
逆向工程与安全分析:
-
恶意软件分析: 病毒、木马等恶意软件通常以可执行文件的形式传播。分析它们的行为,需要反汇编这些文件,阅读其汇编代码。
-
漏洞分析与利用: 发现软件中的安全漏洞(如缓冲区溢出、格式字符串漏洞)并编写攻击代码(exploit)时,需要深入理解汇编代码的执行流程和内存布局。
-
-
跨语言接口: 在不同语言之间进行通信(例如,Python调用C库,或者C库调用特定的硬件指令),有时需要在汇编层面进行接口适配。
-
理解编译器原理: 学习汇编是理解编译器后端(代码生成和优化)如何工作的基石。当您看到C代码如何一步步转化为汇编代码时,编译器的复杂性会变得更加清晰。
汇编语言可能一开始显得枯燥和复杂,但它是理解计算机系统真正的“通用语”。
2.3 汇编语言基础概念:机器的构成要素
要阅读和编写汇编语言,我们需要了解一些核心概念。我们将以x86-64架构为例,介绍其基本构成。
2.3.1 寄存器(Registers)
寄存器是CPU内部的高速存储单元。它们是CPU直接操作数据的场所,比内存(RAM)的访问速度快很多。现代CPU通常有几十甚至上百个寄存器,用于存储各种数据和状态信息。
在x86-64架构中,常见的通用寄存器有:
-
通用寄存器: 用于存储整数、地址等数据。
-
RAX
(Accumulator Register): 累加器,通常用于存储函数返回值或算术操作结果。 -
RBX
(Base Register): 基址寄存器,通用。 -
RCX
(Count Register): 计数器,常用于循环计数或函数参数(第四个)。 -
RDX
(Data Register): 数据寄存器,通用,常用于算术操作或函数参数(第三个)。 -
RSI
(Source Index Register): 源索引寄存器,常用于数据复制操作的源地址,或函数参数(第二个)。 -
RDI
(Destination Index Register): 目的索引寄存器,常用于数据复制操作的目的地址,或函数参数(第一个)。 -
RBP
(Base Pointer Register): 栈基址指针,指向当前栈帧的底部。 -
RSP
(Stack Pointer Register): 栈顶指针,指向当前栈的顶部。 -
R8
-R15
: 额外的通用寄存器,常用于函数参数(第5到第8个)或通用数据存储。
注意: 每个64位通用寄存器(如
RAX
)都有对应的32位(EAX
)、16位(AX
)和8位(AL
/AH
)子寄存器,用于处理不同大小的数据。例如,RAX
是64位,EAX
是RAX
的低32位,AX
是EAX
的低16位,AL
是AX
的低8位,AH
是AX
的高8位。 -
-
指令指针寄存器(RIP - Instruction Pointer): 存储下一条要执行的指令的内存地址。CPU会不断地从
RIP
指向的地址取指令、执行、然后更新RIP
指向下一条指令,实现程序的顺序执行。当遇到跳转指令时,RIP
的值会被修改为跳转目标地址。 -
标志寄存器(RFLAGS): 存储CPU执行算术或逻辑运算后的各种状态标志,如:
-
ZF
(Zero Flag):零标志,运算结果为零时置1。 -
CF
(Carry Flag):进位标志,无符号数运算溢出时置1。 -
OF
(Overflow Flag):溢出标志,有符号数运算溢出时置1。 -
SF
(Sign Flag):符号标志,结果为负数时置1。 这些标志常用于条件跳转指令,以实现if-else
和while
循环等高级语言控制结构。
-
2.3.2 内存寻址(Memory Addressing)
程序运行时,数据和指令都存储在内存中。汇编语言通过各种寻址模式来访问内存中的数据。
-
物理内存与虚拟内存: 操作系统为每个程序提供一个虚拟地址空间,程序操作的都是虚拟地址。操作系统负责将虚拟地址映射到实际的物理内存地址。汇编语言中的地址通常指虚拟地址。
-
常见的内存区域:
-
代码段(.text): 存放可执行指令。
-
数据段(.data): 存放已初始化(如全局变量、静态变量)的数据。
-
BSS段(.bss): 存放未初始化(如全局变量、静态变量)的数据(在程序加载时会被清零)。
-
堆(Heap): 用于动态内存分配(
malloc
/free
)。 -
栈(Stack): 用于存储函数参数、局部变量、函数返回地址等,遵循“后进先出”(LIFO)原则。
-
-
寻址模式示例(x86-64):
-
立即数寻址: 直接在指令中指定常数值。
MOV EAX, 10
(将立即数10移动到EAX寄存器) -
寄存器寻址: 操作数直接就是某个寄存器。
ADD RBX, RCX
(将RCX的值加到RBX) -
直接内存寻址: 直接在指令中指定内存地址。
MOV EAX, [0x12345678]
(将内存地址0x12345678处的值移动到EAX) -
寄存器间接寻址: 寄存器中存储的是内存地址。
MOV EAX, [RBX]
(将RBX寄存器中存储的地址处的值移动到EAX) -
基址变址寻址:
[基址寄存器 + 变址寄存器 * 比例因子 + 偏移量]
,常用于访问数组元素或结构体成员。MOV AL, [RSI + RAX * 4 + 8]
(将RSI指向的基址加上(RAX*4)的偏移量,再加8字节偏移量处的一个字节移动到AL)。
-
2.3.3 指令集(Instruction Set)
指令集是CPU能够执行的所有指令的集合。每条指令都执行一个特定的基本操作。汇编语言指令通常由**操作码(Opcode)和操作数(Operands)**组成。
-
常见指令类型:
-
数据传输指令:
-
MOV dest, src
:将源操作数的值移动到目的操作数。这是最常用的指令。 -
PUSH src
:将源操作数的值压入栈顶。 -
POP dest
:将栈顶的值弹出到目的操作数。
-
-
算术指令:
-
ADD dest, src
:dest = dest + src
-
SUB dest, src
:dest = dest - src
-
MUL src
:无符号乘法,结果存储在特定寄存器对中。 -
IMUL src
:有符号乘法。 -
DIV src
:无符号除法。 -
IDIV src
:有符号除法。 -
INC dest
:dest = dest + 1
-
DEC dest
:dest = dest - 1
-
-
逻辑指令:
-
AND dest, src
:位与 -
OR dest, src
:位或 -
XOR dest, src
:位异或 -
NOT dest
:位非 -
SHL dest, count
:左移 -
SHR dest, count
:右移
-
-
比较指令:
-
CMP op1, op2
:比较两个操作数,但不存储结果,只设置标志寄存器(RFLAGS)。 -
TEST op1, op2
:逻辑与操作,但不存储结果,只设置标志寄存器。
-
-
控制流指令:
-
JMP target
:无条件跳转到指定的目标地址。 -
JCC target
:条件跳转,CC
代表条件码(如JE
(Jump if Equal),JNE
(Jump if Not Equal),JL
(Jump if Less) 等),根据标志寄存器的状态决定是否跳转。 -
CALL target
:调用函数,将当前指令的下一条指令地址(返回地址)压入栈,然后跳转到目标地址。 -
RET
:从函数返回,从栈中弹出返回地址,并跳转到该地址。 -
LOOP target
:循环指令,结合ECX/RCX
寄存器进行计数循环。
-
-
系统调用指令:
-
SYSCALL
(Linux x86-64):触发操作系统内核服务。 -
INT 0x80
(Linux x86-32):早期的系统调用方式。
-
-
2.3.4 数据表示(Data Representation)
汇编语言直接操作内存中的原始字节。理解数据如何表示至关重要:
-
二进制(Binary): 0和1。
-
十六进制(Hexadecimal): 0-9, A-F,通常以
0x
前缀表示,如0xFF
。 -
字节(Byte): 8位。
-
字(Word): 16位。
-
双字(Double Word): 32位。
-
四字(Quad Word): 64位。
-
浮点数: IEEE 754标准(单精度32位,双精度64位)。
2.3.5 栈帧(Stack Frame)
栈是程序运行时非常重要的内存区域,用于支持函数调用。每次函数被调用时,系统都会在栈上为该函数分配一块内存区域,称为栈帧(Stack Frame)或活动记录(Activation Record)。
栈帧通常包含:
-
函数参数: 传递给函数的实参。
-
返回地址: 调用函数返回时,程序应该继续执行的地址。
-
局部变量: 函数内部定义的局部变量。
-
保存的寄存器: 如果被调用函数会修改某些调用者希望保持不变的寄存器(caller-saved registers),或者被调用函数内部使用的寄存器(callee-saved registers),它们的值会在进入函数时被保存到栈上,并在函数返回前恢复。
RBP
(Base Pointer) 寄存器通常用于指向当前栈帧的底部,而 RSP
(Stack Pointer) 寄存器始终指向栈的顶部(栈的生长方向通常是从高地址到低地址)。
2.4 简单的x86-64汇编代码示例与解析
让我们通过几个简单的C语言程序,并查看它们对应的x86-64汇编代码,来直观感受汇编语言。我们将使用 gcc
编译器来生成汇编代码。
示例1:简单的整数加法函数
C语言代码 (sum.c
):
// sum.c
// 简单的整数加法函数示例
int add(int a, int b) {
int result = a + b;
return result;
}
int main() {
int x = 10;
int y = 20;
int z = add(x, y);
return 0; // 程序退出
}
使用 gcc -S sum.c
命令生成汇编代码 (sum.s
):
gcc -S sum.c -o sum.s
```sum.s` (截选并添加中文注释):
```assembly
.file "sum.c" // 源代码文件名为 sum.c
.text // .text 段:存放可执行代码(指令)
.globl add // 声明 add 函数是全局的,可被外部链接
.type add, @function // 声明 add 是一个函数
add:
.LFB0: // .LFB0 是一个局部函数起始标签 (Local Function Begin)
.cfi_startproc // 栈帧信息开始 (用于调试)
pushq %rbp // 将当前函数调用者的 %rbp 寄存器值压栈,保存其栈基址
.cfi_def_cfa_offset 16 // 更新栈帧规则:栈指针相对于栈基址的偏移量
.cfi_offset 6, -16 // 注册 %rbp 寄存器在栈中的位置
movq %rsp, %rbp // 将 %rsp 的值移动到 %rbp,设置当前函数的栈基址
.cfi_def_cfa_register 6 // 更新栈帧规则:当前栈指针通过 %rbp 寄存器来引用
// 函数参数传递:在x86-64 Linux ABI中,前6个整数或指针参数通过寄存器传递
// %rdi 接收第一个参数 (a)
// %rsi 接收第二个参数 (b)
// %rax 用于返回整数结果
movl %edi, -4(%rbp) // 将 %edi (参数 a 的低32位) 移动到栈帧中 %rbp-4 的位置 (局部变量 a)
movl %esi, -8(%rbp) // 将 %esi (参数 b 的低32位) 移动到栈帧中 %rbp-8 的位置 (局部变量 b)
// int result = a + b;
movl -4(%rbp), %eax // 将栈帧中 %rbp-4 的值 (a) 移动到 %eax
addl -8(%rbp), %eax // 将栈帧中 %rbp-8 的值 (b) 加到 %eax (%eax = a + b)
movl %eax, -12(%rbp) // 将 %eax 的结果存储到栈帧中 %rbp-12 的位置 (局部变量 result)
// return result;
movl -12(%rbp), %eax // 将栈帧中 %rbp-12 的值 (result) 移动到 %eax (作为函数返回值)
popq %rbp // 恢复调用者的 %rbp 寄存器值 (从栈中弹出)
.cfi_restore 6 // 更新栈帧规则:恢复 %rbp 寄存器
.cfi_def_cfa 7, 8 // 更新栈帧规则:栈指针相对于栈基址的偏移量
ret // 返回到调用函数(从栈中弹出返回地址,并跳转到该地址)
.cfi_endproc // 栈帧信息结束
.LFE0: // .LFE0 是一个局部函数结束标签 (Local Function End)
.size add, .-add // 声明 add 函数的大小
.globl main // 声明 main 函数是全局的
.type main, @function // 声明 main 是一个函数
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp // 为 main 函数的局部变量 (x, y, z) 分配 16 字节栈空间
// int x = 10;
movl $10, -4(%rbp) // 将立即数 10 移动到栈帧中 %rbp-4 的位置 (局部变量 x)
// int y = 20;
movl $20, -8(%rbp) // 将立即数 20 移动到栈帧中 %rbp-8 的位置 (局部变量 y)
// int z = add(x, y);
movl -4(%rbp), %edi // 将栈帧中 %rbp-4 的值 (x) 移动到 %edi (作为 add 函数的第一个参数)
movl -8(%rbp), %esi // 将栈帧中 %rbp-8 的值 (y) 移动到 %esi (作为 add 函数的第二个参数)
call add@PLT // 调用 add 函数 (@PLT 表示通过过程链接表调用,用于外部函数)
movl %eax, -12(%rbp) // 将 add 函数的返回值 (%eax) 存储到栈帧中 %rbp-12 的位置 (局部变量 z)
// return 0;
movl $0, %eax // 将立即数 0 移动到 %eax (作为 main 函数的返回值)
leave // 相当于 movq %rbp, %rsp; popq %rbp (恢复栈帧和栈指针)
.cfi_def_cfa 7, 8
ret // 返回到调用者 (操作系统)
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0" // 编译器版本信息
.section .note.GNU-stack,"",@progbits
解析:
-
函数调用约定(ABI): 可以看到
add
函数的参数a
和b
分别通过edi
和esi
寄存器传递(这是 x86-64 Linux ABI 的约定)。返回值通过eax
寄存器返回。 -
栈帧:
pushq %rbp
和movq %rsp, %rbp
是典型的函数入口序言,用于建立新的栈帧。subq $16, %rsp
为局部变量x
,y
,z
分配栈空间。leave
指令(或等价的movq %rbp, %rsp; popq %rbp
)是函数出口收尾,用于恢复调用者的栈帧。 -
内存访问: 局部变量
a
,b
,result
,x
,y
,z
都被存储在栈帧中,通过相对于rbp
的负偏移量来访问(例如-4(%rbp)
)。 -
指令:
movl
(move long,移动32位数据),addl
(add long,32位加法),call
(函数调用),ret
(函数返回) 等指令清晰地对应了C代码的逻辑。
示例2:打印 "Hello, World!" (使用系统调用)
C语言代码 (hello.c
):
// hello.c
// 打印 "Hello, World!" 的简单C程序
#include <stdio.h> // 包含标准输入输出库,使用printf
int main() {
printf("Hello, World!\n");
return 0;
}
使用 gcc -S hello.c
命令生成汇编代码 (hello.s
):
gcc -S hello.c -o hello.s
```hello.s` (截选并添加中文注释):
```assembly
.file "hello.c"
.text
.section .rodata // .rodata 段:存放只读数据,如字符串常量
.LC0:
.string "Hello, World!\n" // 定义字符串常量 "Hello, World!\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp // 分配栈空间,可能是为了对齐或未来局部变量
// 调用 printf("Hello, World!\n");
// %rdi 是第一个参数寄存器
leaq .LC0(%rip), %rdi // 将字符串常量 .LC0 的地址加载到 %rdi (作为 printf 的第一个参数)
// %rip 相对寻址:相对于当前指令指针的偏移量寻址
movl $0, %eax // 清空 %eax,表示 printf 没有浮点数参数
call printf@PLT // 调用 printf 函数
// return 0;
movl $0, %eax // 将立即数 0 移动到 %eax (作为 main 函数的返回值)
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
解析:
-
只读数据段(.rodata): 字符串常量
"Hello, World!\n"
被放置在.rodata
段中,这是一个只读的内存区域,以防止程序意外修改字符串内容。 -
leaq .LC0(%rip), %rdi
:leaq
(Load Effective Address) 指令不是移动数据,而是计算有效地址。它将.LC0
标签(即字符串常量)相对于当前指令指针rip
的地址加载到rdi
寄存器。这是C语言中将字符串字面量作为参数传递给函数(如printf
)的典型方式。 -
call printf@PLT
: 调用printf
函数。@PLT
表示通过过程链接表(Procedure Linkage Table)调用,这是动态链接库(如libc
)中函数的调用方式。 -
movl $0, %eax
: 在调用printf
之前,eax
被设置为0,这符合 x86-64 System V ABI(应用程序二进制接口)中对变长参数函数调用时AL
寄存器用于指示浮点参数数量的约定。对于printf
,因为它没有浮点参数,所以设置为0。
通过这两个简单的例子,您可以看到C语言的抽象是如何层层剥离,最终展现在汇编语言的指令细节之中。
3. 编译原理的初步感知:C到汇编的桥梁
编译器是连接高级语言和机器语言的桥梁。理解编译原理,就是理解这座桥梁是如何建造和运作的。它将一个复杂的翻译过程分解为一系列相对独立但又紧密协作的阶段。
3.1 编译器的角色:从C语言到汇编语言的翻译器
从宏观上看,编译器扮演着将程序员编写的**源程序(C代码)转换为目标程序(汇编代码或机器码)**的角色。这个过程不仅仅是简单的“词典查询”式翻译,它涉及到对源代码的深入理解、结构分析、语义检查以及复杂的优化。
3.2 编译的典型阶段(高层次回顾)
在第一部分的引言中,我们已经简要提及了编译器的主要阶段。在这里,我们将它们与“C到汇编”的转化过程联系起来:
-
预处理(Preprocessing):
-
作用: 在真正的编译开始之前,对源代码进行文本替换和文件操作。
-
主要任务:
-
宏展开(Macro Expansion): 将
#define
定义的宏替换为其内容。例如,#define PI 3.14
会将代码中的PI
替换为3.14
。 -
文件包含(File Inclusion): 将
#include
指令替换为指定头文件的内容。 -
条件编译(Conditional Compilation): 根据
#if
,#ifdef
,#ifndef
等指令,选择性地包含或排除部分代码。
-
-
输入: 原始C源代码(
.c
文件)。 -
输出: 经过预处理的C源代码(通常是临时的
.i
文件),它是一个纯粹的C代码文件,不再包含任何预处理指令。
示例:
gcc -E hello.c -o hello.i
可以看到预处理后的文件内容。 -
-
编译(Compilation):
-
作用: 这是核心的翻译阶段,将预处理后的C源代码翻译成汇编语言代码。
-
主要任务(编译器前端和后端):
-
词法分析: 将字符流分解为词法单元(Token)。(我们第一部分已深入探讨)
-
语法分析: 将词法单元流组织成抽象语法树(AST)。
-
语义分析: 检查程序的语义合法性(类型检查、作用域检查等),并收集更多信息填充到AST中。
-
中间代码生成: 将AST转换为一种独立于机器的中间表示(IR)。
-
代码优化: 对IR进行各种转换,以提高程序性能或减小代码大小。
-
目标代码生成: 将优化后的IR翻译成特定CPU架构的汇编语言代码。
-
-
输入: 预处理后的C源代码(
.i
文件)。 -
输出: 汇编语言代码文件(
.s
文件)。
示例:
gcc -S hello.i -o hello.s
或直接gcc -S hello.c -o hello.s
可以看到这个阶段的输出。 -
-
汇编(Assembly):
-
作用: 将汇编语言代码翻译成机器语言的目标文件。
-
主要任务: 将汇编指令的助记符转换为对应的二进制机器码,并处理数据、地址等。
-
输入: 汇编语言代码文件(
.s
文件)。 -
输出: 可重定位目标文件(
.o
文件),包含机器码和符号表信息,但尚未解决所有外部引用。
示例:
gcc -c hello.s -o hello.o
可以看到这个阶段的输出。 -
-
链接(Linking):
-
作用: 将多个目标文件以及程序所需的库文件(静态库或动态库)组合在一起,生成最终的可执行文件。
-
主要任务:
-
符号解析: 解决目标文件中未定义的符号引用(例如,
main
函数中调用的printf
函数在libc.a
或libc.so
中定义)。 -
地址重定位: 为所有代码和数据分配最终的内存地址。
-
-
输入: 一个或多个
.o
文件,以及所需的库文件。 -
输出: 可执行文件(Linux下无后缀,Windows下是
.exe
)。
示例:
gcc hello.o -o hello
或直接gcc hello.c -o hello
(自动执行上述所有步骤)。 -
整个过程如下图所示:
graph TD
A[源代码 .c] --> B(预处理器 cpp);
B --> C[预处理后代码 .i];
C --> D(编译器 cc1);
D --> E[汇编代码 .s];
E --> F(汇编器 as);
F --> G[目标文件 .o];
G --> H(链接器 ld);
H --> I[可执行文件];
subgraph 编译过程核心阶段
D
end
从C语言代码到可执行文件,是一个复杂而精密的工程。每一步都承载着特定的任务,将高层抽象逐步转化为机器可识别的低层指令。理解这些阶段,是理解汇编语言和编译原理的基石。
4. 动手实践:C代码到汇编代码的转换与分析
现在,让我们通过一个更具体的C语言代码示例,动手生成其汇编代码,并详细分析它,以加深对C语言结构如何在汇编层面体现的理解。
我们将创建一个C文件,其中包含变量声明、赋值、简单的条件判断和循环。
C语言代码 (flow_control.c
):
// flow_control.c
// 演示C语言的变量、条件判断和循环在汇编中的表示
int calculate_sum(int limit) {
int sum = 0; // 声明并初始化局部变量 sum
int i = 0; // 声明并初始化循环变量 i
// while 循环
while (i <= limit) {
sum = sum + i; // 算术运算和赋值
i = i + 1; // 递增 i
}
// if-else 条件判断
if (sum > 100) {
return sum * 2; // 如果 sum > 100, 返回 sum 的两倍
} else {
return sum; // 否则,返回 sum 本身
}
}
int main() {
int n = 50;
int final_result = calculate_sum(n);
// 这里可以进一步使用 final_result,但为了简化,直接退出
return 0;
}
使用 gcc -S flow_control.c
命令生成汇编代码 (flow_control.s
):
gcc -S flow_control.c -o flow_control.s
```flow_control.s` (截选并添加中文注释,重点关注 `calculate_sum` 函数):
```assembly
.file "flow_control.c"
.text
.globl calculate_sum // 声明 calculate_sum 为全局函数
.type calculate_sum, @function
calculate_sum:
.LFB0:
.cfi_startproc
pushq %rbp // 保存旧的RBP
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp // 设置新的RBP,指向当前栈帧底部
.cfi_def_cfa_register 6
subq $16, %rsp // 为局部变量分配16字节栈空间 (sum 和 i)
// %rbp-4 为 sum, %rbp-8 为 i, %rbp-16 (可能为对齐或未使用)
// int limit; (参数 limit 在 %edi 中)
movl %edi, -4(%rbp) // 将 %edi (参数 limit) 存储到栈帧中的 %rbp-4 位置
// int sum = 0;
movl $0, -8(%rbp) // 将立即数 0 存储到栈帧中的 %rbp-8 位置 (局部变量 sum)
// int i = 0;
movl $0, -12(%rbp) // 将立即数 0 存储到栈帧中的 %rbp-12 位置 (局部变量 i)
// while (i <= limit) {
.L2: // 循环开始的标签 (L2)
movl -12(%rbp), %eax // 将 i (在 %rbp-12) 的值加载到 %eax
cmpl -4(%rbp), %eax // 比较 %eax (i) 和 limit (在 %rbp-4)
jg .L3 // 如果 i > limit (ZF=0且SF=OF), 则跳转到 .L3 (跳出循环)
// 注意:`jg` 是有符号比较的“大于”,`jle` 是“小于等于”
// C语言 `i <= limit` 等价于 `!(i > limit)`
// sum = sum + i;
movl -8(%rbp), %eax // 将 sum (在 %rbp-8) 的值加载到 %eax
addl -12(%rbp), %eax // 将 i (在 %rbp-12) 的值加到 %eax (%eax = sum + i)
movl %eax, -8(%rbp) // 将结果存回 sum (在 %rbp-8)
// i = i + 1; (或 i++;)
addl $1, -12(%rbp) // 将立即数 1 加到 i (在 %rbp-12)
jmp .L2 // 无条件跳转回循环开始的标签 .L2
.L3: // 循环结束后的标签 (L3)
// if (sum > 100) {
movl -8(%rbp), %eax // 将 sum (在 %rbp-8) 的值加载到 %eax
cmpl $100, %eax // 比较 %eax (sum) 和立即数 100
jle .L4 // 如果 sum <= 100 (ZF=1或SF!=OF), 则跳转到 .L4 (else 分支)
// return sum * 2; (if 分支)
movl -8(%rbp), %eax // 将 sum (在 %rbp-8) 的值加载到 %eax
addl %eax, %eax // %eax = %eax + %eax (等价于 %eax * 2)
jmp .L5 // 无条件跳转到 .L5 (函数返回前清理栈帧)
.L4: // else 分支的标签 (L4)
// return sum; (else 分支)
movl -8(%rbp), %eax // 将 sum (在 %rbp-8) 的值加载到 %eax
.L5: // 函数返回前的公共清理标签 (L5)
leave // 恢复栈帧 (%rsp = %rbp; popq %rbp)
.cfi_def_cfa 7, 8
ret // 返回到调用者
.cfi_endproc
.LFE0:
.size calculate_sum, .-calculate_sum
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
// int n = 50;
movl $50, -4(%rbp) // 将立即数 50 移动到栈帧中的 %rbp-4 位置 (局部变量 n)
// int final_result = calculate_sum(n);
movl -4(%rbp), %edi // 将 n (在 %rbp-4) 的值移动到 %edi (作为 calculate_sum 的参数 limit)
call calculate_sum@PLT // 调用 calculate_sum 函数
movl %eax, -8(%rbp) // 将返回值 (%eax) 存储到栈帧中的 %rbp-8 位置 (局部变量 final_result)
// return 0;
movl $0, %eax // 将立即数 0 移动到 %eax (main 函数的返回值)
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
关键分析点:
-
局部变量与栈:
calculate_sum
函数中的sum
和i
变量,以及main
函数中的n
和final_result
,都被分配在栈上。它们的地址是相对于rbp
的负偏移量(例如-8(%rbp)
,-12(%rbp)
)。 -
参数传递:
calculate_sum(int limit)
的参数limit
在调用时通过edi
寄存器传递,并在函数内部被movl %edi, -4(%rbp)
存储到栈上,以供后续操作。 -
循环(
while
): C语言的while (i <= limit)
被翻译成了L2
和L3
两个标签,以及cmpl
(compare) 和jg
(jump if greater) 指令。-
L2
是循环体的入口。 -
cmpl -4(%rbp), %eax
比较i
和limit
。 -
jg .L3
如果i > limit
,则跳到L3
,退出循环。 -
循环体内的
sum = sum + i;
和i = i + 1;
都对应了movl
和addl
指令。 -
jmp .L2
实现循环的跳回。
-
-
条件判断(
if-else
): C语言的if (sum > 100)
被翻译成了L3
(if-else 的共同入口),L4
(else 分支的入口),L5
(函数返回的公共出口),以及cmpl
和jle
(jump if less or equal) 指令。-
cmpl $100, %eax
比较sum
和100
。 -
jle .L4
如果sum <= 100
,则跳到L4
(else 分支)。 -
sum * 2
在汇编中优化成了addl %eax, %eax
(自身相加,比乘法指令更快)。 -
jmp .L5
确保if
分支执行完后跳过else
分支。
-
-
函数返回: 函数的返回值最终通过
eax
寄存器返回给调用者。leave
和ret
指令完成栈帧的清理和程序控制权的返回。
通过这种方式,您可以看到C语言中的高级概念是如何一步步被“扁平化”和“原子化”为汇编指令,在CPU的寄存器和内存上进行操作的。
结语:迈向底层世界的第一步
在本系列的第一部分中,我们首先反思了C语言的抽象层次,认识到虽然C语言强大且接近硬件,但在追求极致性能、进行底层系统开发、或进行逆向工程时,仍需更深入地了解计算机的运作机制。
随后,我们揭开了汇编语言的神秘面纱,理解了它作为机器语言符号化表示的本质,以及学习它所能带来的巨大优势:从根本上理解程序执行、进行细致的性能优化、参与底层系统构建、以及进行安全分析。我们还学习了汇编语言的核心概念,包括寄存器、内存寻址、指令集以及函数调用中至关重要的栈帧。
最后,我们通过实际的C语言代码和其对应的x86-64汇编输出,直观地看到了C语言中的变量、算术运算、条件判断和循环是如何被编译器翻译成汇编指令的。这使得我们对编译器的“魔法”有了初步的感知,理解了C代码如何一步步转化为汇编代码,以及汇编器和链接器在最终生成可执行文件中的作用。
这仅仅是探索的开始。在接下来的系列中,我们将继续深入:
-
第二部分: 更深入地探索编译器的前端,特别是词法分析和语法分析的完整实现细节,包括如何用代码构建词法分析器和递归下降解析器,并生成更完整的抽象语法树。
-
第三部分: 聚焦编译器的语义分析和中间代码生成,理解编译器如何检查程序逻辑的合法性、进行类型推断,并将抽象语法树转换为独立于机器的中间表示。
-
第四部分: 深入探讨编译器的代码优化技术,了解各种优化算法如何提升程序的性能和效率。
-
第五部分: 讲解编译器的目标代码生成,以及如何将优化后的中间代码转化为特定CPU架构的汇编指令,并讨论运行时环境、链接器和加载器的作用。
通过这个系列,您将不仅能够理解“比C语言更底层”的汇编和编译原理是什么,更能够掌握如何从零开始构建一个简易的C语言编译器,从而真正“彻底搞懂”计算机程序从高级语言到机器执行的整个生命周期。
(第二部分:编译器的前端魔法)
引言:从机器语言到语言的理解——编译器的前端之旅
在系列的第一部分,我们从宏观上审视了C语言的抽象层次,并初步揭开了汇编语言与编译原理的神秘面纱。我们了解到,C代码最终要转化为CPU能理解的机器码,而编译器正是完成这一“魔法”的关键。我们还通过简单的C代码示例,观察了它们被GCC编译器转换为x86-64汇编代码后的样子,对底层执行有了直观的感知。
现在,是时候深入编译器内部,探究它的具体工作流程了。编译器的整个工作流程通常被划分为若干个阶段,其中最先执行、也是最基础的两个阶段构成了编译器的“前端”:词法分析(Lexical Analysis)和语法分析(Syntax Analysis)。
想象一下,你正试图理解一本用一种陌生语言写成的书。
-
词法分析就像你学会了如何识别这种语言中的“单词”——哪些字母组合起来是一个有意义的词,哪些是标点符号,哪些是数字,哪些是注释。你不需要知道这些词的含义,只需要把它们从连续的字符流中分离出来。
-
语法分析则像你学习了这种语言的“语法规则”——单词是如何组合成短语、句子和段落的。你开始识别主谓宾、动词短语、从句结构。即使你可能还不完全理解句子的深层含义,但你已经能判断哪些句子是合法的,哪些是语法错误的。
这两个阶段是后续所有高级分析(如语义分析、代码优化)的基础。它们将人类编写的扁平的、连续的文本源代码,逐步转化为结构化的、机器更易处理的形式。本部分,我们将详细讲解这两个阶段的原理,并亲手编写一个简化的C语言编译器前端,让您能够深入体验这些“魔法”的实现细节。
我们将覆盖以下内容:
-
词法分析的深入回顾与C语言实现: 详细剖析词法分析器的工作机制,并提供一个具备缓冲、错误处理和多类型Token识别能力的C语言实现。
-
语法分析的理论基础与递归下降实现: 讲解上下文无关文法、抽象语法树,并基于递归下降方法,构建一个能解析C语言子集的语法分析器,生成AST。
-
抽象语法树(AST)的详细设计: 深入探讨AST的节点类型、结构及其在编译器中的作用。
-
前端代码整合与完整示例: 将词法分析器和语法分析器整合起来,并用实际的C代码演示整个前端的工作流程。
通过本部分的学习,您将能够:
-
掌握词法分析和语法分析的核心原理和常用实现技术。
-
理解源代码如何从字符流被转化为有结构、有层次的抽象表示。
-
亲手编写并运行一个简化的编译器前端,为后续的语义分析和代码生成打下坚实基础。
1. 词法分析的深入回顾与C语言实现
词法分析(Lexical Analysis),也称为扫描(Scanning),是编译器识别源代码的“第一道门槛”。它的任务是读取输入字符流,将其划分为一个个有意义的词法单元(Tokens),并过滤掉注释和空白字符。
1.1 核心概念回顾
在第一部分,我们已经简要介绍了词法分析中的三个重要概念:
-
词素(Lexeme): 源代码中匹配某个模式的实际字符序列。例如,在
int count = 10;
中,"int"
、"count"
、"="
、"10"
、";"
都是词素。 -
模式(Pattern): 描述一类词素的规则,通常用正则表达式来定义。例如,标识符的模式是
[a-zA-Z_][a-zA-Z0-9_]*
。 -
词法单元(Token): 词法分析器的输出。它是一个逻辑结构,包含词素的类型(如
TOKEN_KEYWORD_INT
、TOKEN_IDENTIFIER
)和可选的属性值(如标识符的名称字符串,整数的数值)。
词法分析器是上下文无关的,它只关心字符序列是否符合预定义的模式,不关心这些词法单元组合起来是否具有语法意义或语义意义。例如,int = 10;
是词法合法的,即使它在语法上是错误的。
1.2 词法分析器设计的关键考虑
一个健壮的词法分析器需要考虑以下关键点:
-
高效的字符读取: 直接从文件逐字符读取会导致频繁的I/O操作。因此,通常采用输入缓冲区,一次性读取一大块字符到内存中,然后从缓冲区中逐个读取。当缓冲区耗尽时,再从文件读取下一块。
-
“向前看”(Lookahead): 某些词法单元的识别需要查看当前字符之后的若干个字符才能确定。例如,识别
/
后面是/
(单行注释)、*
(多行注释)还是其他字符(除法操作符)。 -
词素的收集: 识别词素时,需要将匹配的字符逐个收集起来,形成完整的词素字符串。
-
错误处理: 检测并报告词法错误,例如非法字符、未终止的字符串/注释。
-
位置追踪: 记录每个词法单元在源代码中的行号和列号,这对后续的错误报告和调试至关重要。
-
关键字识别: 识别出符合标识符模式的词素后,需要判断它是否是语言的保留关键字。
1.3 词法分析器C语言实现细节
现在,我们将提供一个完整的C语言词法分析器实现。为了清晰地演示核心逻辑,我们采用一种模块化的方式,将头文件(声明)和源文件(实现)分开。
1.3.1 lexer.h
:词法分析器的接口定义
这个头文件定义了词法分析器所需的所有数据结构(TokenType
, Token
, KeywordEntry
)和函数声明。
// 文件名称: lexer.h
// 词法分析器的接口定义,包括Token类型、Token结构体和所有外部函数声明
#ifndef LEXER_H
#define LEXER_H
// 引入标准C库头文件,提供必要的类型和函数
#include <stdio.h> // 用于文件操作 (FILE, EOF, fopen, fclose, fread), printf, fprintf, perror
#include <stdlib.h> // 用于内存管理 (malloc, free, exit), 字符串转数字 (atol, atof)
#include <string.h> // 用于字符串操作 (strdup, strcmp, strcat, sprintf)
#include <ctype.h> // 用于字符判断 (isalpha, isdigit, isalnum, isspace)
#include <stdbool.h> // 用于布尔类型 (bool, true, false)
#include <errno.h> // 用于错误码 (errno)
// --- Token 类型枚举 (TokenType) ---
// 定义C语言中所有可能出现的词法单元的类型。
typedef enum {
// 特殊类型
TOKEN_EOF = 0, // 文件结束 (End-Of-File)
TOKEN_ERROR, // 词法错误,表示识别到非法字符序列
// --- 关键字 (Keywords) ---
TOKEN_KEYWORD_INT, // int
TOKEN_KEYWORD_VOID, // void
TOKEN_KEYWORD_RETURN, // return
TOKEN_KEYWORD_IF, // if
TOKEN_KEYWORD_ELSE, // else
TOKEN_KEYWORD_WHILE, // while
TOKEN_KEYWORD_FOR, // for
TOKEN_KEYWORD_CHAR, // char
TOKEN_KEYWORD_FLOAT, // float
TOKEN_KEYWORD_DOUBLE, // double
TOKEN_KEYWORD_STRUCT, // struct
TOKEN_KEYWORD_UNION, // union
TOKEN_KEYWORD_ENUM, // enum
TOKEN_KEYWORD_TYPEDEF, // typedef
TOKEN_KEYWORD_CONST, // const
TOKEN_KEYWORD_STATIC, // static
TOKEN_KEYWORD_EXTERN, // extern
TOKEN_KEYWORD_SIZEOF, // sizeof
TOKEN_KEYWORD_BREAK, // break
TOKEN_KEYWORD_CONTINUE, // continue
TOKEN_KEYWORD_SWITCH, // switch
TOKEN_KEYWORD_CASE, // case
TOKEN_KEYWORD_DEFAULT, // default
TOKEN_KEYWORD_GOTO, // goto
TOKEN_KEYWORD_DO, // do
TOKEN_KEYWORD_LONG, // long
TOKEN_KEYWORD_SHORT, // short
TOKEN_KEYWORD_SIGNED, // signed
TOKEN_KEYWORD_UNSIGNED, // unsigned
TOKEN_KEYWORD_VOLATILE, // volatile
TOKEN_KEYWORD_REGISTER, // register
TOKEN_KEYWORD_AUTO, // auto
TOKEN_KEYWORD_EXTEND, // _Bool, _Complex, _Imaginary (C99及后续扩展)
// --- 标识符 (Identifier) ---
TOKEN_IDENTIFIER, // 变量名、函数名等用户定义的名称
// --- 字面量 (Literals / Constants) ---
TOKEN_LITERAL_INT, // 整数常量,如 123, 0xFF
TOKEN_LITERAL_FLOAT, // 浮点数常量,如 3.14, 1.2e-5
TOKEN_LITERAL_CHAR, // 字符常量,如 'a', '\n'
TOKEN_LITERAL_STRING, // 字符串常量,如 "hello world"
// --- 操作符 (Operators) ---
TOKEN_OP_PLUS, // +
TOKEN_OP_MINUS, // -
TOKEN_OP_MULTIPLY, // *
TOKEN_OP_DIVIDE, // /
TOKEN_OP_MODULO, // %
TOKEN_OP_ASSIGN, // =
TOKEN_OP_EQ, // == (等于)
TOKEN_OP_NE, // != (不等于)
TOKEN_OP_LT, // < (小于)
TOKEN_OP_LE, // <= (小于等于)
TOKEN_OP_GT, // > (大于)
TOKEN_OP_GE, // >= (大于等于)
TOKEN_OP_AND_LOGICAL, // && (逻辑与)
TOKEN_OP_OR_LOGICAL, // || (逻辑或)
TOKEN_OP_NOT_LOGICAL, // ! (逻辑非)
TOKEN_OP_BITWISE_AND, // & (位与)
TOKEN_OP_BITWISE_OR, // | (位或)
TOKEN_OP_BITWISE_XOR, // ^ (位异或)
TOKEN_OP_BITWISE_NOT, // ~ (位非)
TOKEN_OP_LSHIFT, // << (左移位)
TOKEN_OP_RSHIFT, // >> (右移位)
TOKEN_OP_INCREMENT, // ++ (增量)
TOKEN_OP_DECREMENT, // -- (减量)
TOKEN_OP_ARROW, // -> (结构体指针成员访问)
TOKEN_OP_DOT, // . (结构体成员访问)
TOKEN_OP_COMMA, // , (逗号操作符/分隔符)
TOKEN_OP_QUESTION, // ? (条件操作符三目符号)
TOKEN_OP_COLON, // : (条件操作符三目符号)
TOKEN_OP_ASSIGN_PLUS, // +=
TOKEN_OP_ASSIGN_MINUS, // -=
TOKEN_OP_ASSIGN_MULTIPLY, // *=
TOKEN_OP_ASSIGN_DIVIDE, // /=
TOKEN_OP_ASSIGN_MODULO, // %=
TOKEN_OP_ASSIGN_AND_BITWISE, // &=
TOKEN_OP_ASSIGN_OR_BITWISE, // |=
TOKEN_OP_ASSIGN_XOR_BITWISE, // ^=
TOKEN_OP_ASSIGN_LSHIFT, // <<=
TOKEN_OP_ASSIGN_RSHIFT, // >>=
// --- 分隔符 (Delimiters / Punctuators) ---
TOKEN_DELIM_SEMICOLON, // ;
TOKEN_DELIM_LPAREN, // (
TOKEN_DELIM_RPAREN, // )
TOKEN_DELIM_LBRACE, // {
TOKEN_DELIM_RBRACE, // }
TOKEN_DELIM_LBRACKET, // [
TOKEN_DELIM_RBRACKET, // ]
TOKEN_DELIM_ELLIPSIS, // ... (C99 可变参数列表)
// --- 其他内部使用的Token (不作为输出) ---
TOKEN_NEWLINE_INTERNAL // 内部用于标记换行,不作为真正的Token传递给Parser
} TokenType;
// --- Token 结构体 ---
// 表示一个词法单元,包含其类型、原始文本(词素)、位置信息和可选的属性值。
typedef struct Token {
TokenType type; // 词法单元的类型
char *lexeme; // 词法单元在源代码中的原始字符串(动态分配,需要释放)
union { // 联合体,用于存储与Token类型相关的属性值
long int_val; // 如果是整数常量,存储其长整型数值
double float_val; // 如果是浮点数常量,存储其双精度浮点数值
// 对于标识符,其名称存储在 lexeme 中,无需额外字段
// 对于字符串常量,其内容存储在 lexeme 中
} value;
int line; // 词法单元在源代码中的起始行号 (从1开始)
int col; // 词法单元在源代码中的起始列号 (从1开始)
} Token;
// --- 关键字映射结构体 ---
// 用于将字符串关键字映射到对应的TokenType。
typedef struct {
const char *name; // 关键字字符串
TokenType type; // 对应的Token类型
} KeywordEntry;
// --- 词法分析器全局状态变量 (为简化示例而使用,实际生产中应封装在结构体中) ---
// 缓冲区大小,决定一次从文件读取多少字符。
#define LEXER_BUFFER_SIZE 4096
extern char lexer_input_buffer[LEXER_BUFFER_SIZE]; // 字符输入缓冲区
extern int lexer_buffer_pos; // 缓冲区当前读取位置
extern int lexer_buffer_fill; // 缓冲区实际填充的字符数量
extern int lexer_line_num; // 当前扫描的行号
extern int lexer_col_num; // 当前扫描的列号
extern FILE *lexer_source_file; // 源代码文件指针
// --- 词法分析器函数声明 ---
/**
* @brief 初始化词法分析器,打开指定源代码文件并设置初始状态。
* @param filename 要分析的源代码文件路径。
*/
void init_lexer(const char *filename);
/**
* @brief 关闭词法分析器,释放资源。
*/
void close_lexer();
/**
* @brief 从输入流中获取下一个字符,并更新行号和列号。
* 处理缓冲区填充逻辑。
* @return 获取到的字符,文件结束时返回 EOF。
*/
char get_char();
/**
* @brief 窥视输入流中的下一个字符,但不消耗它。
* @return 窥视到的字符,文件结束时返回 EOF。
*/
char peek_char();
/**
* @brief 将一个字符放回输入流。
* @param c 要放回的字符。
*/
void unget_char(char c);
/**
* @brief 报告词法错误并终止程序。
* @param message 错误信息。
* @param line 错误发生的行号。
* @param col 错误发生的列号。
*/
void lexer_error(const char *message, int line, int col);
/**
* @brief 创建并初始化一个新的Token对象。
* @param type Token类型。
* @param lexeme 词素字符串。
* @param line 行号。
* @param col 列号。
* @return 新创建的Token指针。
*/
Token *create_token(TokenType type, const char *lexeme, int line, int col);
/**
* @brief 释放Token对象占用的内存,包括其内部的词素字符串。
* @param token 要释放的Token指针。
*/
void free_token(Token *token);
/**
* @brief 跳过源代码中的所有空白字符 (空格, 制表符, 换行符等)。
*/
void skip_whitespace();
/**
* @brief 跳过源代码中的所有C语言注释 (单行注释 // 和多行注释 /* ... * /)。
*/
void skip_comments();
/**
* @brief 获取输入流中的下一个词法单元。
* 这是词法分析器的核心函数,负责识别所有类型的Token。
* @return 识别到的Token指针。
*/
Token *get_next_token();
/**
* @brief 辅助函数:将TokenType枚举值转换为可读的字符串。
* @param type 要转换的TokenType。
* @return 对应的字符串表示。
*/
const char *token_type_to_string(TokenType type);
/**
* @brief 辅助函数:打印Token的详细信息,用于调试。
* @param token 要打印的Token指针。
*/
void print_token_info(Token *token);
#endif // LEXER_H
1.3.2 lexer.c
:词法分析器的具体实现
这个源文件包含了 lexer.h
中所有函数的具体实现逻辑。它涵盖了字符输入/输出、关键字查找、以及各种Token类型的识别。
// 文件名称: lexer.c
// 词法分析器的具体实现,负责将源代码字符流转换为Token流。
#include "lexer.h" // 引入词法分析器头文件
// --- 全局词法分析器状态变量定义 ---
char lexer_input_buffer[LEXER_BUFFER_SIZE];
int lexer_buffer_pos = 0;
int lexer_buffer_fill = 0;
int lexer_line_num = 1;
int lexer_col_num = 1;
FILE *lexer_source_file = NULL;
// --- 关键字列表 (静态,只读) ---
// 存储C语言所有关键字及其对应的TokenType,用于识别。
static KeywordEntry keywords[] = {
{"auto", TOKEN_KEYWORD_AUTO}, {"break", TOKEN_KEYWORD_BREAK}, {"case", TOKEN_KEYWORD_CASE},
{"char", TOKEN_KEYWORD_CHAR}, {"const", TOKEN_KEYWORD_CONST}, {"continue", TOKEN_KEYWORD_CONTINUE},
{"default", TOKEN_KEYWORD_DEFAULT}, {"do", TOKEN_KEYWORD_DO}, {"double", TOKEN_KEYWORD_DOUBLE},
{"else", TOKEN_KEYWORD_ELSE}, {"enum", TOKEN_KEYWORD_ENUM}, {"extern", TOKEN_KEYWORD_EXTERN},
{"float", TOKEN_KEYWORD_FLOAT}, {"for", TOKEN_KEYWORD_FOR}, {"goto", TOKEN_KEYWORD_GOTO},
{"if", TOKEN_KEYWORD_IF}, {"int", TOKEN_KEYWORD_INT}, {"long", TOKEN_KEYWORD_LONG},
{"register", TOKEN_KEYWORD_REGISTER}, {"return", TOKEN_KEYWORD_RETURN}, {"short", TOKEN_KEYWORD_SHORT},
{"signed", TOKEN_KEYWORD_SIGNED}, {"sizeof", TOKEN_KEYWORD_SIZEOF}, {"static", TOKEN_KEYWORD_STATIC},
{"struct", TOKEN_KEYWORD_STRUCT}, {"switch", TOKEN_KEYWORD_SWITCH}, {"typedef", TOKEN_KEYWORD_TYPEDEF},
{"union", TOKEN_KEYWORD_UNION}, {"unsigned", TOKEN_KEYWORD_UNSIGNED}, {"void", TOKEN_KEYWORD_VOID},
{"volatile", TOKEN_KEYWORD_VOLATILE}, {"while", TOKEN_KEYWORD_WHILE},
{"_Bool", TOKEN_KEYWORD_EXTEND}, // C99布尔类型
{"_Complex", TOKEN_KEYWORD_EXTEND}, // C99复数类型
{"_Imaginary", TOKEN_KEYWORD_EXTEND}, // C99虚数类型
// 可以根据需要添加更多关键字
{NULL, TOKEN_ERROR} // 哨兵值,标记数组结束
};
// --- 词法分析器初始化与关闭 ---
/**
* @brief 初始化词法分析器。
* 打开源代码文件,并重置内部缓冲区和位置追踪变量。
* @param filename 要打开的源代码文件路径。
*/
void init_lexer(const char *filename) {
lexer_source_file = fopen(filename, "r");
if (lexer_source_file == NULL) {
perror("Error opening source file");
exit(EXIT_FAILURE);
}
lexer_buffer_pos = 0;
lexer_buffer_fill = 0;
lexer_line_num = 1;
lexer_col_num = 1;
printf("Lexer initialized. Reading from file: %s\n", filename);
}
/**
* @brief 关闭词法分析器。
* 关闭已打开的文件。
*/
void close_lexer() {
if (lexer_source_file != NULL) {
fclose(lexer_source_file);
lexer_source_file = NULL;
printf("Lexer closed.\n");
}
}
// --- 字符输入/输出管理 ---
/**
* @brief 从输入流中获取下一个字符。
* 首先尝试从缓冲区获取。如果缓冲区空了,则从文件填充缓冲区。
* 同时更新行号和列号。
* @return 下一个字符,或EOF。
*/
char get_char() {
if (lexer_buffer_pos >= lexer_buffer_fill) {
// 缓冲区空了,从文件读取
size_t bytes_read = fread(lexer_input_buffer, 1, LEXER_BUFFER_SIZE, lexer_source_file);
if (bytes_read == 0) {
if (feof(lexer_source_file)) {
return EOF; // 文件结束
} else {
perror("Error reading from source file");
exit(EXIT_FAILURE);
}
}
lexer_buffer_fill = bytes_read;
lexer_buffer_pos = 0;
}
char c = lexer_input_buffer[lexer_buffer_pos++];
if (c == '\n') {
lexer_line_num++;
lexer_col_num = 1;
} else if (c != EOF) {
lexer_col_num++;
}
return c;
}
/**
* @brief 窥视输入流中的下一个字符,不消耗它。
* @return 下一个字符,或EOF。
*/
char peek_char() {
// 确保缓冲区中有字符可供窥视,逻辑与get_char类似
if (lexer_buffer_pos >= lexer_buffer_fill) {
size_t bytes_read = fread(lexer_input_buffer, 1, LEXER_BUFFER_SIZE, lexer_source_file);
if (bytes_read == 0) {
if (feof(lexer_source_file)) {
return EOF;
} else {
perror("Error reading from source file for peek");
exit(EXIT_FAILURE);
}
}
lexer_buffer_fill = bytes_read;
lexer_buffer_pos = 0;
}
return lexer_input_buffer[lexer_buffer_pos];
}
/**
* @brief 将一个字符放回输入流。
* @param c 要放回的字符。
*/
void unget_char(char c) {
if (lexer_buffer_pos > 0) {
lexer_buffer_pos--;
lexer_input_buffer[lexer_buffer_pos] = c;
// 回退时,行号和列号也需要回退
if (c == '\n') {
lexer_line_num--;
// 列号回退到上一行末尾是复杂的,这里简化处理,实际需要更精确的追踪
// 或重新计算(不推荐)
// 在实际编译器中,会维护一个精确的位置栈或通过其他方式处理
lexer_col_num = -1; // 标记为不精确,表示需要重新同步
} else if (lexer_col_num > 1) {
lexer_col_num--;
}
} else {
// 无法回退,通常是逻辑错误
lexer_error("Cannot unget character: buffer position at start or logic error.", lexer_line_num, lexer_col_num);
exit(EXIT_FAILURE);
}
}
// --- 错误报告 ---
/**
* @brief 报告词法错误并终止程序。
* @param message 错误信息。
* @param line 错误发生的行号。
* @param col 错误发生的列号。
*/
void lexer_error(const char *message, int line, int col) {
fprintf(stderr, "Lexer Error at line %d, column %d: %s\n", line, col, message);
exit(EXIT_FAILURE);
}
// --- Token 对象管理 ---
/**
* @brief 创建并初始化一个新的Token对象。
* 为Token结构体及其lexeme字符串分配内存。
* @param type Token类型。
* @param lexeme 词素字符串。
* @param line 行号。
* @param col 列号。
* @return 新创建的Token指针。
*/
Token *create_token(TokenType type, const char *lexeme, int line, int col) {
Token *newToken = (Token *)malloc(sizeof(Token));
if (newToken == NULL) {
perror("Failed to allocate memory for Token");
exit(EXIT_FAILURE);
}
newToken->type = type;
newToken->line = line;
newToken->col = col;
if (lexeme != NULL) {
newToken->lexeme = strdup(lexeme);
if (newToken->lexeme == NULL) {
perror("Failed to allocate memory for token lexeme (strdup)");
free(newToken);
exit(EXIT_FAILURE);
}
} else {
newToken->lexeme = NULL;
}
return newToken;
}
/**
* @brief 释放Token对象占用的内存。
* @param token 要释放的Token指针。
*/
void free_token(Token *token) {
if (token != NULL) {
if (token->lexeme != NULL) {
free(token->lexeme);
token->lexeme = NULL;
}
free(token);
}
}
// --- 辅助识别函数:跳过空白和注释 ---
/**
* @brief 跳过所有空白字符。
*/
void skip_whitespace() {
char c;
while ((c = get_char()) != EOF) {
if (!isspace(c)) {
unget_char(c); // 非空白字符放回
break;
}
}
}
/**
* @brief 跳过C语言注释。
* 支持单行 // 和多行 /* ... * / 注释。
*/
void skip_comments() {
char c = get_char();
if (c != '/') {
unget_char(c); // 不是注释开头
return;
}
char next_c = peek_char();
if (next_c == '/') { // 单行注释 //
get_char(); // 消耗第二个 '/'
while ((c = get_char()) != EOF && c != '\n');
unget_char(c); // 将换行符放回,以便get_char更新行号
skip_whitespace(); // 递归跳过后续空白
skip_comments(); // 递归检查是否还有连续注释
} else if (next_c == '*') { // 多行注释 /* ... */
get_char(); // 消耗第二个 '*'
int comment_start_line = lexer_line_num;
int comment_start_col = lexer_col_num - 1; // 指向 '*' 的位置
bool end_found = false;
while ((c = get_char()) != EOF) {
if (c == '*') {
if (peek_char() == '/') {
get_char(); // 消耗 '/'
end_found = true;
break;
}
}
}
if (!end_found) {
lexer_error("Unterminated multi-line comment.", comment_start_line, comment_start_col);
}
skip_whitespace(); // 递归跳过后续空白
skip_comments(); // 递归检查是否还有连续注释
} else {
unget_char(c); // 只是一个除号操作符 '/',放回
}
}
// --- 主要Token识别函数 ---
/**
* @brief 识别标识符或关键字。
* @return Token指针。
*/
static Token *recognize_identifier_or_keyword_internal() {
int start_line = lexer_line_num;
int start_col = lexer_col_num - 1; // 词素起始列
char lexeme_buffer[256]; // 临时缓冲区,假设标识符最长255字符
int buffer_idx = 0;
char c = get_char(); // 第一个字符已确定是字母或下划线
lexeme_buffer[buffer_idx++] = c;
while ((c = peek_char()) != EOF && (isalnum(c) || c == '_')) {
lexeme_buffer[buffer_idx++] = get_char();
if (buffer_idx >= sizeof(lexeme_buffer) - 1) { // 留一个位置给'\0'
lexer_error("Identifier or keyword too long.", start_line, start_col);
return NULL;
}
}
lexeme_buffer[buffer_idx] = '\0';
// 检查是否是关键字
for (int i = 0; keywords[i].name != NULL; ++i) {
if (strcmp(lexeme_buffer, keywords[i].name) == 0) {
return create_token(keywords[i].type, lexeme_buffer, start_line, start_col);
}
}
// 否则是标识符
return create_token(TOKEN_IDENTIFIER, lexeme_buffer, start_line, start_col);
}
/**
* @brief 识别数字常量 (整数或浮点数)。
* @return Token指针。
*/
static Token *recognize_number_internal() {
int start_line = lexer_line_num;
int start_col = lexer_col_num - 1;
char lexeme_buffer[256]; // 临时缓冲区
int buffer_idx = 0;
bool is_float = false;
char c;
// 读取整数部分
while ((c = peek_char()) != EOF && isdigit(c)) {
lexeme_buffer[buffer_idx++] = get_char();
if (buffer_idx >= sizeof(lexeme_buffer) - 1) {
lexer_error("Numeric literal too long.", start_line, start_col);
return NULL;
}
}
// 检查小数点
if (peek_char() == '.') {
get_char(); // 消耗 '.'
lexeme_buffer[buffer_idx++] = '.';
is_float = true;
while ((c = peek_char()) != EOF && isdigit(c)) {
lexeme_buffer[buffer_idx++] = get_char();
if (buffer_idx >= sizeof(lexeme_buffer) - 1) {
lexer_error("Numeric literal too long after decimal point.", start_line, start_col);
return NULL;
}
}
}
// 检查科学计数法 (e/E)
c = peek_char();
if (c == 'e' || c == 'E') {
get_char(); // 消耗 'e'/'E'
lexeme_buffer[buffer_idx++] = c;
is_float = true;
c = peek_char();
if (c == '+' || c == '-') { // 符号
lexeme_buffer[buffer_idx++] = get_char();
}
bool has_exponent_digits = false;
while ((c = peek_char()) != EOF && isdigit(c)) {
lexeme_buffer[buffer_idx++] = get_char();
has_exponent_digits = true;
if (buffer_idx >= sizeof(lexeme_buffer) - 1) {
lexer_error("Numeric literal too long in exponent.", start_line, start_col);
return NULL;
}
}
if (!has_exponent_digits) {
lexer_error("Missing digits in exponent part of float literal.", start_line, start_col);
return NULL;
}
}
lexeme_buffer[buffer_idx] = '\0';
Token *token = NULL;
if (is_float) {
token = create_token(TOKEN_LITERAL_FLOAT, lexeme_buffer, start_line, start_col);
token->value.float_val = atof(lexeme_buffer);
} else {
token = create_token(TOKEN_LITERAL_INT, lexeme_buffer, start_line, start_col);
token->value.int_val = atol(lexeme_buffer);
}
return token;
}
/**
* @brief 识别字符串常量。
* @return Token指针。
*/
static Token *recognize_string_literal_internal() {
int start_line = lexer_line_num;
int start_col = lexer_col_num - 1;
char content_buffer[1024]; // 假设字符串内容最长1023字符
int buffer_idx = 0;
char c;
// 起始双引号已在get_next_token中消耗
while ((c = get_char()) != EOF && c != '"') {
if (c == '\\') { // 转义字符
char escaped_char = get_char();
switch (escaped_char) {
case 'n': content_buffer[buffer_idx++] = '\n'; break;
case 't': content_buffer[buffer_idx++] = '\t'; break;
case 'r': content_buffer[buffer_idx++] = '\r'; break;
case '\\': content_buffer[buffer_idx++] = '\\'; break;
case '"': content_buffer[buffer_idx++] = '"'; break;
case '\'': content_buffer[buffer_idx++] = '\''; break;
case '0': content_buffer[buffer_idx++] = '\0'; break; // 注意:字符串中的'\0'
// TODO: 更多转义字符如八进制\ooo, 十六进制\xhh
default:
lexer_error("Unknown escape sequence in string literal.", lexer_line_num, lexer_col_num - 1);
return NULL;
}
} else {
content_buffer[buffer_idx++] = c;
}
if (buffer_idx >= sizeof(content_buffer) - 1) {
lexer_error("String literal content too long.", start_line, start_col);
return NULL;
}
}
if (c == EOF) {
lexer_error("Unterminated string literal (missing closing '\"').", start_line, start_col);
return NULL;
}
content_buffer[buffer_idx] = '\0';
// 重新构建带引号的完整词素,包括转义字符的原始表示
char full_lexeme[1026]; // 1024 + 2引号 + \0
sprintf(full_lexeme, "\"%s\"", content_buffer);
return create_token(TOKEN_LITERAL_STRING, full_lexeme, start_line, start_col);
}
/**
* @brief 识别字符常量。
* @return Token指针。
*/
static Token *recognize_char_literal_internal() {
int start_line = lexer_line_num;
int start_col = lexer_col_num - 1;
char char_val = 0;
char lexeme_buffer[10]; // 足够存储 '\xFF' 这类转义字符加引号
int buffer_idx = 0;
// 起始单引号已在get_next_token中消耗
char c = get_char(); // 读取字符常量的第一个字符
if (c == '\\') { // 转义字符
char escaped_char = get_char();
switch (escaped_char) {
case 'n': char_val = '\n'; break;
case 't': char_val = '\t'; break;
case 'r': char_val = '\r'; break;
case '\\': char_val = '\\'; break;
case '"': char_val = '"'; break;
case '\'': char_val = '\''; break;
case '0': char_val = '\0'; break;
// TODO: 更多转义字符如八进制\ooo, 十六进制\xhh
default:
lexer_error("Unknown escape sequence in char literal.", lexer_line_num, lexer_col_num - 1);
return NULL;
}
sprintf(lexeme_buffer, "'\\%c'", escaped_char); // 构造词素,如 '\n' -> "'\n'"
} else {
char_val = c;
sprintf(lexeme_buffer, "'%c'", c); // 构造词素,如 'a' -> "'a'"
}
char closing_quote = get_char();
if (closing_quote == EOF || closing_quote != '\'') {
lexer_error("Unterminated char literal (missing closing '\'').", start_line, start_col);
return NULL;
}
Token *token = create_token(TOKEN_LITERAL_CHAR, lexeme_buffer, start_line, start_col);
token->value.int_val = (long)char_val; // 存储字符的ASCII值
return token;
}
/**
* @brief 识别操作符或分隔符。
* @return Token指针。
*/
static Token *recognize_operator_or_delimiter_internal() {
int start_line = lexer_line_num;
int start_col = lexer_col_num - 1;
char c = get_char();
char next_c = peek_char();
char lexeme_buffer[4]; // 最长如 "<<="
lexeme_buffer[0] = c;
lexeme_buffer[1] = '\0';
TokenType type;
switch (c) {
case '+':
if (next_c == '+') { get_char(); strcat(lexeme_buffer, "+"); type = TOKEN_OP_INCREMENT; }
else if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_PLUS; }
else type = TOKEN_OP_PLUS;
break;
case '-':
if (next_c == '-') { get_char(); strcat(lexeme_buffer, "-"); type = TOKEN_OP_DECREMENT; }
else if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_MINUS; }
else if (next_c == '>') { get_char(); strcat(lexeme_buffer, ">"); type = TOKEN_OP_ARROW; }
else type = TOKEN_OP_MINUS;
break;
case '*':
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_MULTIPLY; }
else type = TOKEN_OP_MULTIPLY;
break;
case '/': // 注解已在skip_comments中处理,这里只剩下除号
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_DIVIDE; }
else type = TOKEN_OP_DIVIDE;
break;
case '%':
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_MODULO; }
else type = TOKEN_OP_MODULO;
break;
case '=':
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_EQ; }
else type = TOKEN_OP_ASSIGN;
break;
case '!':
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_NE; }
else type = TOKEN_OP_NOT_LOGICAL;
break;
case '<':
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_LE; }
else if (next_c == '<') {
get_char(); strcat(lexeme_buffer, "<");
if (peek_char() == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_LSHIFT; }
else type = TOKEN_OP_LSHIFT;
}
else type = TOKEN_OP_LT;
break;
case '>':
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_GE; }
else if (next_c == '>') {
get_char(); strcat(lexeme_buffer, ">");
if (peek_char() == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_RSHIFT; }
else type = TOKEN_OP_RSHIFT;
}
else type = TOKEN_OP_GT;
break;
case '&':
if (next_c == '&') { get_char(); strcat(lexeme_buffer, "&"); type = TOKEN_OP_AND_LOGICAL; }
else if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_AND_BITWISE; }
else type = TOKEN_OP_BITWISE_AND;
break;
case '|':
if (next_c == '|') { get_char(); strcat(lexeme_buffer, "|"); type = TOKEN_OP_OR_LOGICAL; }
else if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_OR_BITWISE; }
else type = TOKEN_OP_BITWISE_OR;
break;
case '^':
if (next_c == '=') { get_char(); strcat(lexeme_buffer, "="); type = TOKEN_OP_ASSIGN_XOR_BITWISE; }
else type = TOKEN_OP_BITWISE_XOR;
break;
case '~': type = TOKEN_OP_BITWISE_NOT; break;
case '?': type = TOKEN_OP_QUESTION; break;
case ':': type = TOKEN_OP_COLON; break;
case ';': type = TOKEN_DELIM_SEMICOLON; break;
case '(': type = TOKEN_DELIM_LPAREN; break;
case ')': type = TOKEN_DELIM_RPAREN; break;
case '{': type = TOKEN_DELIM_LBRACE; break;
case '}': type = TOKEN_DELIM_RBRACE; break;
case '[': type = TOKEN_DELIM_LBRACKET; break;
case ']': type = TOKEN_DELIM_RBRACKET; break;
case '.':
if (next_c == '.') { // 可能是 "..."
get_char(); // 消耗第二个 '.'
if (peek_char() == '.') { // 是 "..."
get_char(); strcat(lexeme_buffer, ".."); type = TOKEN_DELIM_ELLIPSIS;
} else { // 是 ".." 但不是 "...",目前不合法,或者小数点后不是数字
lexer_error("Invalid sequence after dot (expected '...' or digit).", start_line, start_col);
return create_token(TOKEN_ERROR, lexeme_buffer, start_line, start_col);
}
} else { // 单独的 "."
type = TOKEN_OP_DOT;
}
break;
case ',': type = TOKEN_OP_COMMA; break;
default:
lexeme_buffer[0] = c;
lexeme_buffer[1] = '\0';
lexer_error("Unrecognized character.", start_line, start_col);
return create_token(TOKEN_ERROR, lexeme_buffer, start_line, start_col);
}
return create_token(type, lexeme_buffer, start_line, start_col);
}
// --- 核心词法分析器函数 ---
/**
* @brief 获取输入流中的下一个词法单元 (Token)。
* 这是词法分析器对外的主要接口。
* @return 识别到的Token指针,文件结束时返回TOKEN_EOF。
*/
Token *get_next_token() {
while (true) {
skip_whitespace(); // 跳过空白
skip_comments(); // 跳过注释
skip_whitespace(); // 再次跳过可能由注释引入的空白
int start_line = lexer_line_num;
int start_col = lexer_col_num;
char c = peek_char(); // 窥视下一个字符
if (c == EOF) {
return create_token(TOKEN_EOF, "EOF", start_line, start_col);
}
if (isalpha(c) || c == '_') {
return recognize_identifier_or_keyword_internal();
} else if (isdigit(c)) {
return recognize_number_internal();
} else if (c == '"') {
get_char(); // 消耗起始双引号
return recognize_string_literal_internal();
} else if (c == '\'') {
get_char(); // 消耗起始单引号
return recognize_char_literal_internal();
} else {
// 剩下的可能是操作符或分隔符
return recognize_operator_or_delimiter_internal();
}
}
}
// --- 调试辅助函数 ---
/**
* @brief 将TokenType枚举值转换为可读的字符串。
* @param type 要转换的TokenType。
* @return 对应的字符串表示。
*/
const char *token_type_to_string(TokenType type) {
switch (type) {
case TOKEN_EOF: return "TOKEN_EOF";
case TOKEN_ERROR: return "TOKEN_ERROR";
// 关键字
case TOKEN_KEYWORD_INT: return "TOKEN_KEYWORD_INT";
case TOKEN_KEYWORD_VOID: return "TOKEN_KEYWORD_VOID";
case TOKEN_KEYWORD_RETURN: return "TOKEN_KEYWORD_RETURN";
case TOKEN_KEYWORD_IF: return "TOKEN_KEYWORD_IF";
case TOKEN_KEYWORD_ELSE: return "TOKEN_KEYWORD_ELSE";
case TOKEN_KEYWORD_WHILE: return "TOKEN_KEYWORD_WHILE";
case TOKEN_KEYWORD_FOR: return "TOKEN_KEYWORD_FOR";
case TOKEN_KEYWORD_CHAR: return "TOKEN_KEYWORD_CHAR";
case TOKEN_KEYWORD_FLOAT: return "TOKEN_KEYWORD_FLOAT";
case TOKEN_KEYWORD_DOUBLE: return "TOKEN_KEYWORD_DOUBLE";
case TOKEN_KEYWORD_STRUCT: return "TOKEN_KEYWORD_STRUCT";
case TOKEN_KEYWORD_UNION: return "TOKEN_KEYWORD_UNION";
case TOKEN_KEYWORD_ENUM: return "TOKEN_KEYWORD_ENUM";
case TOKEN_KEYWORD_TYPEDEF: return "TOKEN_KEYWORD_TYPEDEF";
case TOKEN_KEYWORD_CONST: return "TOKEN_KEYWORD_CONST";
case TOKEN_KEYWORD_STATIC: return "TOKEN_KEYWORD_STATIC";
case TOKEN_KEYWORD_EXTERN: return "TOKEN_KEYWORD_EXTERN";
case TOKEN_KEYWORD_SIZEOF: return "TOKEN_KEYWORD_SIZEOF";
case TOKEN_KEYWORD_BREAK: return "TOKEN_KEYWORD_BREAK";
case TOKEN_KEYWORD_CONTINUE: return "TOKEN_KEYWORD_CONTINUE";
case TOKEN_KEYWORD_SWITCH: return "TOKEN_KEYWORD_SWITCH";
case TOKEN_KEYWORD_CASE: return "TOKEN_KEYWORD_CASE";
case TOKEN_KEYWORD_DEFAULT: return "TOKEN_KEYWORD_DEFAULT";
case TOKEN_KEYWORD_GOTO: return "TOKEN_KEYWORD_GOTO";
case TOKEN_KEYWORD_DO: return "TOKEN_KEYWORD_DO";
case TOKEN_KEYWORD_LONG: return "TOKEN_KEYWORD_LONG";
case TOKEN_KEYWORD_SHORT: return "TOKEN_KEYWORD_SHORT";
case TOKEN_KEYWORD_SIGNED: return "TOKEN_KEYWORD_SIGNED";
case TOKEN_KEYWORD_UNSIGNED: return "TOKEN_KEYWORD_UNSIGNED";
case TOKEN_KEYWORD_VOLATILE: return "TOKEN_KEYWORD_VOLATILE";
case TOKEN_KEYWORD_REGISTER: return "TOKEN_KEYWORD_REGISTER";
case TOKEN_KEYWORD_AUTO: return "TOKEN_KEYWORD_AUTO";
case TOKEN_KEYWORD_EXTEND: return "TOKEN_KEYWORD_EXTEND";
// 标识符
case TOKEN_IDENTIFIER: return "TOKEN_IDENTIFIER";
// 字面量
case TOKEN_LITERAL_INT: return "TOKEN_LITERAL_INT";
case TOKEN_LITERAL_FLOAT: return "TOKEN_LITERAL_FLOAT";
case TOKEN_LITERAL_CHAR: return "TOKEN_LITERAL_CHAR";
case TOKEN_LITERAL_STRING: return "TOKEN_LITERAL_STRING";
// 操作符
case TOKEN_OP_PLUS: return "TOKEN_OP_PLUS";
case TOKEN_OP_MINUS: return "TOKEN_OP_MINUS";
case TOKEN_OP_MULTIPLY: return "TOKEN_OP_MULTIPLY";
case TOKEN_OP_DIVIDE: return "TOKEN_OP_DIVIDE";
case TOKEN_OP_MODULO: return "TOKEN_OP_MODULO";
case TOKEN_OP_ASSIGN: return "TOKEN_OP_ASSIGN";
case TOKEN_OP_EQ: return "TOKEN_OP_EQ";
case TOKEN_OP_NE: return "TOKEN_OP_NE";
case TOKEN_OP_LT: return "TOKEN_OP_LT";
case TOKEN_OP_LE: return "TOKEN_OP_LE";
case TOKEN_OP_GT: return "TOKEN_OP_GT";
case TOKEN_OP_GE: return "TOKEN_OP_GE";
case TOKEN_OP_AND_LOGICAL: return "TOKEN_OP_AND_LOGICAL";
case TOKEN_OP_OR_LOGICAL: return "TOKEN_OP_OR_LOGICAL";
case TOKEN_OP_NOT_LOGICAL: return "TOKEN_OP_NOT_LOGICAL";
case TOKEN_OP_BITWISE_AND: return "TOKEN_OP_BITWISE_AND";
case TOKEN_OP_BITWISE_OR: return "TOKEN_OP_BITWISE_OR";
case TOKEN_OP_BITWISE_XOR: return "TOKEN_OP_BITWISE_XOR";
case TOKEN_OP_BITWISE_NOT: return "TOKEN_OP_BITWISE_NOT";
case TOKEN_OP_LSHIFT: return "TOKEN_OP_LSHIFT";
case TOKEN_OP_RSHIFT: return "TOKEN_OP_RSHIFT";
case TOKEN_OP_INCREMENT: return "TOKEN_OP_INCREMENT";
case TOKEN_OP_DECREMENT: return "TOKEN_OP_DECREMENT";
case TOKEN_OP_ARROW: return "TOKEN_OP_ARROW";
case TOKEN_OP_DOT: return "TOKEN_OP_DOT";
case TOKEN_OP_COMMA: return "TOKEN_OP_COMMA";
case TOKEN_OP_QUESTION: return "TOKEN_OP_QUESTION";
case TOKEN_OP_COLON: return "TOKEN_OP_COLON";
case TOKEN_OP_ASSIGN_PLUS: return "TOKEN_OP_ASSIGN_PLUS";
case TOKEN_OP_ASSIGN_MINUS: return "TOKEN_OP_ASSIGN_MINUS";
case TOKEN_OP_ASSIGN_MULTIPLY: return "TOKEN_OP_ASSIGN_MULTIPLY";
case TOKEN_OP_ASSIGN_DIVIDE: return "TOKEN_OP_ASSIGN_DIVIDE";
case TOKEN_OP_ASSIGN_MODULO: return "TOKEN_OP_ASSIGN_MODULO";
case TOKEN_OP_ASSIGN_AND_BITWISE: return "TOKEN_OP_ASSIGN_AND_BITWISE";
case TOKEN_OP_ASSIGN_OR_BITWISE: return "TOKEN_OP_ASSIGN_OR_BITWISE";
case TOKEN_OP_ASSIGN_XOR_BITWISE: return "TOKEN_OP_ASSIGN_XOR_BITWISE";
case TOKEN_OP_ASSIGN_LSHIFT: return "TOKEN_OP_ASSIGN_LSHIFT";
case TOKEN_OP_ASSIGN_RSHIFT: return "TOKEN_OP_ASSIGN_RSHIFT";
// 分隔符
case TOKEN_DELIM_SEMICOLON: return "TOKEN_DELIM_SEMICOLON";
case TOKEN_DELIM_LPAREN: return "TOKEN_DELIM_LPAREN";
case TOKEN_DELIM_RPAREN: return "TOKEN_DELIM_RPAREN";
case TOKEN_DELIM_LBRACE: return "TOKEN_DELIM_LBRACE";
case TOKEN_DELIM_RBRACE: return "TOKEN_DELIM_RBRACE";
case TOKEN_DELIM_LBRACKET: return "TOKEN_DELIM_LBRACKET";
case TOKEN_DELIM_RBRACKET: return "TOKEN_DELIM_RBRACKET";
case TOKEN_DELIM_ELLIPSIS: return "TOKEN_DELIM_ELLIPSIS";
case TOKEN_NEWLINE_INTERNAL: return "TOKEN_NEWLINE_INTERNAL";
default: return "UNKNOWN_TOKEN_TYPE";
}
}
/**
* @brief 打印Token的详细信息,用于调试。
* @param token 要打印的Token指针。
*/
void print_token_info(Token *token) {
if (token == NULL) {
printf("NULL Token\n");
return;
}
printf("Token: Type=%s, Lexeme=\"%s\", Line=%d, Col=%d",
token_type_to_string(token->type),
token->lexeme ? token->lexeme : "(null)",
token->line, token->col);
if (token->type == TOKEN_LITERAL_INT) {
printf(", Value=%ld (int)", token->value.int_val);
} else if (token->type == TOKEN_LITERAL_FLOAT) {
printf(", Value=%f (float)", token->value.float_val);
} else if (token->type == TOKEN_LITERAL_CHAR) {
// 字符常量的值通常存储为它的ASCII整数值
printf(", Value='%c' (char ASCII: %ld)", (char)token->value.int_val, token->value.int_val);
}
printf("\n");
}
词法分析代码量分析:
-
lexer.h
文件大约 130 行。 -
lexer.c
文件(包含所有辅助函数和注释)大约 600 行。 总计词法分析部分代码量约 730 行。这部分代码实现了词法分析的核心功能,包括了缓冲区管理、多种Token类型的识别、转义字符处理以及基本的错误报告。
2. 语法分析的理论基础与递归下降实现
词法分析器将源代码转换为Token流,但这个流本身并没有结构信息。语法分析(Syntax Analysis)的任务就是接收这个Token流,并根据语言的语法规则(通常用上下文无关文法定义),检查Token序列的合法性,并将其组织成一个层次化的结构——抽象语法树(AST)。
2.1 上下文无关文法(Context-Free Grammars, CFGs)
CFG 是描述程序语言语法结构的形式化工具。一个CFG由非终结符、终结符、产生式和开始符号组成。
-
非终结符(Non-terminals): 代表抽象的语法结构,如
Expression
(表达式)、Statement
(语句)、FunctionDefinition
(函数定义)。它们在推导过程中会被替换。 -
终结符(Terminals): 语言中最基本的不可再分解的符号,即词法分析器产生的Token。如
int
,if
,identifier
,+
,;
,{
等。 -
产生式(Productions): 定义了非终结符如何由其他非终结符和终结符组成。形式为
A -> β
,其中A
是非终结符,β
是终结符和非终结符的序列。|
表示“或”。 -
开始符号(Start Symbol): 整个语法的最高层非终结符,通常代表整个程序。
我们简化C语言子集的文法(用于本系列):
为了实现一个简易的编译器,我们关注C语言的一个核心子集:
// 开始符号
Program -> FunctionDefinition* EOF
// 函式定義 (至少一个)
FunctionDefinition -> TypeSpecifier Identifier ( ParameterListOptional ) CompoundStatement
// 型别规范符
TypeSpecifier -> INT_KEYWORD | VOID_KEYWORD | CHAR_KEYWORD | FLOAT_KEYWORD | DOUBLE_KEYWORD
// 参数列表 (可为空)
ParameterListOptional -> ParameterList | ε
ParameterList -> TypeSpecifier Identifier (COMMA TypeSpecifier Identifier)*
// 复合语句 (函数体或代码块)
CompoundStatement -> LBRACE DeclarationListOptional StatementListOptional RBRACE
// 宣告列表 (可为空)
DeclarationListOptional -> Declaration*
Declaration -> TypeSpecifier Identifier (ASSIGN Expression)? SEMICOLON
// 语句列表 (可为空)
StatementListOptional -> Statement*
// 语句
Statement -> ExpressionStatement
| CompoundStatement
| IfStatement
| WhileStatement
| ReturnStatement
| BREAK_KEYWORD SEMICOLON // 增加break
| CONTINUE_KEYWORD SEMICOLON // 增加continue
// 表达式语句
ExpressionStatement -> Expression? SEMICOLON
// If 语句
IfStatement -> IF_KEYWORD LPAREN Expression RPAREN Statement (ELSE_KEYWORD Statement)?
// While 语句
WhileStatement -> WHILE_KEYWORD LPAREN Expression RPAREN Statement
// Return 语句
ReturnStatement -> RETURN_KEYWORD Expression? SEMICOLON
// 表达式 (按照操作符优先级和结合性分解)
// 我们将简化为只有部分操作符,并使用递归下降实现优先级。
// 优先级从低到高:赋值 -> 逻辑或 -> 逻辑与 -> 关系 -> 加减 -> 乘除模 -> 一元 -> 主表达式
Expression -> AssignmentExpression
AssignmentExpression -> ConditionalExpression ( (ASSIGN | ASSIGN_PLUS | ...) AssignmentExpression )?
ConditionalExpression -> LogicalORExpression ( QUESTION Expression COLON ConditionalExpression )?
LogicalORExpression -> LogicalANDExpression ( LOGICAL_OR LogicalANDExpression )*
LogicalANDExpression -> EqualityExpression ( LOGICAL_AND EqualityExpression )*
EqualityExpression -> RelationalExpression ( (EQ | NE) RelationalExpression )*
RelationalExpression -> AdditiveExpression ( (LT | LE | GT | GE) AdditiveExpression )*
AdditiveExpression -> MultiplicativeExpression ( (PLUS | MINUS) MultiplicativeExpression )*
MultiplicativeExpression -> UnaryExpression ( (MULTIPLY | DIVIDE | MODULO) UnaryExpression )*
UnaryExpression -> ( PLUS | MINUS | INCREMENT | DECREMENT | NOT_LOGICAL | BITWISE_NOT ) UnaryExpression
| PrimaryExpression
PrimaryExpression -> IDENTIFIER (LPAREN ArgumentListOptional RPAREN)? // 标识符可以是变量或函数调用
| INT_LITERAL
| FLOAT_LITERAL
| CHAR_LITERAL
| STRING_LITERAL
| LPAREN Expression RPAREN // 括号表达式
// 函式呼叫的参数列表 (可为空)
ArgumentListOptional -> ArgumentList | ε
ArgumentList -> Expression (COMMA Expression)*
2.2 抽象语法树(AST)的设计
AST是源代码的抽象结构化表示,它移除了文法中的“语法糖”(如分号、括号),只保留了对程序语义至关重要的信息。AST是编译器后续阶段(语义分析、代码生成)的核心输入。
2.2.1 ast.h
:AST节点的接口定义
这个头文件定义了 ASTNodeType
枚举和 ASTNode
结构体,以及所有与AST节点操作相关的函数声明。
// 文件名称: ast.h
// 抽象语法树 (AST) 的节点定义和操作函数声明
#ifndef AST_H
#define AST_H
#include "lexer.h" // 引入词法分析器头文件,因为AST节点可能引用Token信息
#include <stddef.h> // for size_t (用于数组大小)
#include <stdio.h> // for FILE* (用于调试打印)
// --- AST 节点类型枚举 (ASTNodeType) ---
// 定义AST中可能表示的各种语法构造。
typedef enum {
// 根节点
AST_PROGRAM, // 整个程序的根节点
// 宣告 (Declarations)
AST_VAR_DECLARATION, // 变量宣告 (int a; int b = 10;)
AST_FUNC_DEFINITION, // 函数定义 (int main() { ... })
AST_PARAM_DECLARATION, // 函数参数宣告 (int x)
// 语句 (Statements)
AST_COMPOUND_STMT, // 复合语句 (代码块), 即 `{ ... }`
AST_EXPR_STMT, // 表达式语句 (x = 5; function_call();)
AST_IF_STMT, // if 语句 (if (...) {...} else {...})
AST_WHILE_STMT, // while 语句 (while (...) {...})
AST_RETURN_STMT, // return 语句 (return expr;)
AST_BREAK_STMT, // break 语句
AST_CONTINUE_STMT, // continue 语句
// 表达式 (Expressions)
AST_IDENTIFIER_EXPR, // 标识符表达式 (变量名,如 x, count)
AST_INT_LITERAL_EXPR, // 整数常量表达式 (如 10, 0xFF)
AST_FLOAT_LITERAL_EXPR, // 浮点数常量表达式 (如 3.14)
AST_CHAR_LITERAL_EXPR, // 字符常量表达式 (如 'a')
AST_STRING_LITERAL_EXPR,// 字符串常量表达式 (如 "hello")
AST_BINARY_OP_EXPR, // 二元操作符表达式 (a + b, x > y)
AST_UNARY_OP_EXPR, // 一元操作符表达式 (++x, -y, !z)
AST_CALL_EXPR, // 函数调用表达式 (func(a, b))
AST_CONDITIONAL_EXPR, // 条件操作符表达式 (a ? b : c)
// 类型
AST_TYPE_SPECIFIER, // 类型规范符 (int, void, char, float, double)
// 错误节点 (用于语法分析错误恢复或标记)
AST_ERROR_NODE // 表示一个语法错误的节点,不代表有效结构
} ASTNodeType;
// 前向声明,因为 ASTNode 会自引用
struct ASTNode;
// --- AST 节点的值联合体 ---
// 存储不同类型AST节点特有的属性。
typedef union ASTNodeValue {
long int_val; // 用于 AST_INT_LITERAL_EXPR
double float_val; // 用于 AST_FLOAT_LITERAL_EXPR
char char_val; // 用于 AST_CHAR_LITERAL_EXPR (通常也存为 int_val)
char *string_val; // 用于 AST_STRING_LITERAL_EXPR (动态分配)
char *identifier_name; // 用于 AST_IDENTIFIER_EXPR (动态分配)
TokenType op_type; // 用于 AST_BINARY_OP_EXPR, AST_UNARY_OP_EXPR,存储操作符的TokenType
TokenType type_specifier_token; // 用于 AST_TYPE_SPECIFIER,存储 int/void/char 等Token类型
} ASTNodeValue;
// --- 抽象语法树节点结构体 ---
// 每个节点都包含其类型、子节点列表,以及可选的特定值。
typedef struct ASTNode {
ASTNodeType type; // 节点类型
struct ASTNode **children; // 指向子节点指针的数组 (动态分配)
size_t num_children; // 子节点的数量
size_t children_capacity; // 子节点数组的当前容量
ASTNodeValue value; // 节点的值 (使用联合体)
int line; // 节点在源代码中的起始行号
int col; // 节点在源代码中的起始列号
} ASTNode;
// --- AST 相关函数声明 ---
/**
* @brief 创建一个指定类型和位置的AST节点。
* @param type 节点类型。
* @param line 节点起始行号。
* @param col 节点起始列号。
* @return 新创建的ASTNode指针。
*/
ASTNode *create_ast_node(ASTNodeType type, int line, int col);
/**
* @brief 为父节点添加一个子节点。
* 动态调整子节点数组大小。
* @param parent 父节点指针。
* @param child 要添加的子节点指针。
*/
void add_child(ASTNode *parent, ASTNode *child);
/**
* @brief 设置AST节点的整数值。
* @param node 节点指针。
* @param val 整数值。
*/
void set_node_int_value(ASTNode *node, long val);
/**
* @brief 设置AST节点的浮点数值。
* @param node 节点指针。
* @param val 浮点数值。
*/
void set_node_float_value(ASTNode *node, double val);
/**
* @brief 设置AST节点的字符值。
* @param node 节点指针。
* @param val 字符值。
*/
void set_node_char_value(ASTNode *node, char val);
/**
* @brief 设置AST节点的字符串值 (标识符或字符串常量内容)。
* 复制字符串,确保独立性。
* @param node 节点指针。
* @param str 字符串。
*/
void set_node_string_value(ASTNode *node, const char *str);
/**
* @brief 设置AST节点的操作符类型。
* @param node 节点指针。
* @param op_type 操作符的TokenType。
*/
void set_node_op_type(ASTNode *node, TokenType op_type);
/**
* @brief 设置AST节点的类型规范符Token类型。
* @param node 节点指针。
* @param type_token 类型规范符的TokenType。
*/
void set_node_type_specifier(ASTNode *node, TokenType type_token);
/**
* @brief 递迴地释放整个AST树及其所有节点的内存。
* @param node 要释放的AST树的根节点。
*/
void free_ast(ASTNode *node);
/**
* @brief 辅助函数:将ASTNodeType枚举转换为可读的字符串。
* @param type AST节点类型。
* @return 对应的字符串。
*/
const char *ast_node_type_to_string(ASTNodeType type);
/**
* @brief 辅助函数:递迴地打印AST树的结构 (用于调试)。
* @param node 当前节点。
* @param indent 缩进层级。
*/
void print_ast(ASTNode *node, int indent);
#endif // AST_H
2.2.2 ast.c
:AST节点的实现
这个源文件包含了 ast.h
中所有函数的核心实现,包括内存管理和树的遍历/打印。
// 文件名称: ast.c
// 抽象语法树 (AST) 节点的具体实现,包括节点的创建、管理和释放。
#include "ast.h" // 引入AST头文件
#include <stdlib.h> // for malloc, free, realloc
#include <string.h> // for strdup
// 子节点数组初始容量和扩容因子
#define INITIAL_CHILDREN_CAPACITY 2
#define CHILDREN_CAPACITY_FACTOR 2
// --- AST 节点创建与管理 ---
/**
* @brief 创建一个指定类型和位置的AST节点。
* 初始化节点的基本属性,子节点数组初始为空。
* @param type 节点类型。
* @param line 节点起始行号。
* @param col 节点起始列号。
* @return 新创建的ASTNode指针。如果内存分配失败,则终止程序。
*/
ASTNode *create_ast_node(ASTNodeType type, int line, int col) {
ASTNode *node = (ASTNode *)malloc(sizeof(ASTNode));
if (node == NULL) {
perror("Failed to allocate memory for ASTNode");
exit(EXIT_FAILURE);
}
node->type = type;
node->children = NULL; // 初始没有子节点
node->num_children = 0;
node->children_capacity = 0; // 初始容量为0
node->line = line;
node->col = col;
return node;
}
/**
* @brief 为父节点添加一个子节点。
* 如果子节点数组容量不足,则自动扩容。
* @param parent 父节点指针,不能为NULL。
* @param child 要添加的子节点指针,不能为NULL。
*/
void add_child(ASTNode *parent, ASTNode *child) {
if (parent == NULL || child == NULL) {
fprintf(stderr, "Error: Cannot add NULL child to parent or parent is NULL.\n");
exit(EXIT_FAILURE);
}
// 检查是否需要扩容
if (parent->num_children >= parent->children_capacity) {
size_t new_capacity = parent->children_capacity == 0 ?
INITIAL_CHILDREN_CAPACITY : parent->children_capacity * CHILDREN_CAPACITY_FACTOR;
ASTNode **new_children = (ASTNode **)realloc(parent->children, sizeof(ASTNode *) * new_capacity);
if (new_children == NULL) {
perror("Failed to reallocate memory for ASTNode children");
exit(EXIT_FAILURE);
}
parent->children = new_children;
parent->children_capacity = new_capacity;
}
// 添加子节点
parent->children[parent->num_children] = child;
parent->num_children++;
}
/**
* @brief 设置AST节点的整数值。
* 用于AST_INT_LITERAL_EXPR类型的节点。
* @param node 节点指针。
* @param val 整数值。
*/
void set_node_int_value(ASTNode *node, long val) {
if (node == NULL) {
fprintf(stderr, "Error: Cannot set value for NULL ASTNode.\n");
exit(EXIT_FAILURE);
}
node->value.int_val = val;
}
/**
* @brief 设置AST节点的浮点数值。
* 用于AST_FLOAT_LITERAL_EXPR类型的节点。
* @param node 节点指针。
* @param val 浮点数值。
*/
void set_node_float_value(ASTNode *node, double val) {
if (node == NULL) {
fprintf(stderr, "Error: Cannot set value for NULL ASTNode.\n");
exit(EXIT_FAILURE);
}
node->value.float_val = val;
}
/**
* @brief 设置AST节点的字符值。
* 用于AST_CHAR_LITERAL_EXPR类型的节点。
* @param node 节点指针。
* @param val 字符值。
*/
void set_node_char_value(ASTNode *node, char val) {
if (node == NULL) {
fprintf(stderr, "Error: Cannot set value for NULL ASTNode.\n");
exit(EXIT_FAILURE);
}
node->value.char_val = val;
}
/**
* @brief 设置AST节点的字符串值 (标识符名称或字符串常量内容)。
* 使用strdup复制字符串,确保AST节点拥有独立的字符串副本。
* @param node 节点指针。
* @param str 要复制的字符串。
*/
void set_node_string_value(ASTNode *node, const char *str) {
if (node == NULL) {
fprintf(stderr, "Error: Cannot set value for NULL ASTNode.\n");
exit(EXIT_FAILURE);
}
if (str != NULL) {
node->value.string_val = strdup(str);
if (node->value.string_val == NULL) {
perror("Failed to duplicate string for ASTNode value");
exit(EXIT_FAILURE);
}
} else {
node->value.string_val = NULL;
}
}
/**
* @brief 设置AST节点的操作符类型。
* 用于AST_BINARY_OP_EXPR或AST_UNARY_OP_EXPR类型的节点。
* @param node 节点指针。
* @param op_type 操作符的TokenType。
*/
void set_node_op_type(ASTNode *node, TokenType op_type) {
if (node == NULL) {
fprintf(stderr, "Error: Cannot set value for NULL ASTNode.\n");
exit(EXIT_FAILURE);
}
node->value.op_type = op_type;
}
/**
* @brief 设置AST节点的类型规范符Token类型。
* 用于AST_TYPE_SPECIFIER类型的节点。
* @param node 节点指针。
* @param type_token 类型规范符的TokenType (例如 TOKEN_KEYWORD_INT)。
*/
void set_node_type_specifier(ASTNode *node, TokenType type_token) {
if (node == NULL) {
fprintf(stderr, "Error: Cannot set value for NULL ASTNode.\n");
exit(EXIT_FAILURE);
}
node->value.type_specifier_token = type_token;
}
/**
* @brief 递迴地释放整个AST树及其所有节点的内存。
* 深度优先遍历,先释放子节点,再释放当前节点。
* @param node 要释放的AST树的根节点。
*/
void free_ast(ASTNode *node) {
if (node == NULL) {
return;
}
// 递归释放所有子节点
for (size_t i = 0; i < node->num_children; ++i) {
free_ast(node->children[i]);
}
// 释放子节点指针数组本身
if (node->children != NULL) {
free(node->children);
node->children = NULL;
}
// 释放特定类型节点中动态分配的字符串
if ((node->type == AST_IDENTIFIER_EXPR || node->type == AST_STRING_LITERAL_EXPR) && node->value.string_val != NULL) {
free(node->value.string_val);
node->value.string_val = NULL;
}
// 最后,释放当前节点本身
free(node);
}
// --- AST 调试辅助函数 ---
/**
* @brief 将ASTNodeType枚举值转换为可读的字符串。
* @param type AST节点类型。
* @return 对应的字符串。
*/
const char *ast_node_type_to_string(ASTNodeType type) {
switch (type) {
case AST_PROGRAM: return "PROGRAM";
case AST_VAR_DECLARATION: return "VAR_DECL";
case AST_FUNC_DEFINITION: return "FUNC_DEF";
case AST_PARAM_DECLARATION: return "PARAM_DECL";
case AST_COMPOUND_STMT: return "COMPOUND_STMT";
case AST_EXPR_STMT: return "EXPR_STMT";
case AST_IF_STMT: return "IF_STMT";
case AST_WHILE_STMT: return "WHILE_STMT";
case AST_RETURN_STMT: return "RETURN_STMT";
case AST_BREAK_STMT: return "BREAK_STMT";
case AST_CONTINUE_STMT: return "CONTINUE_STMT";
case AST_IDENTIFIER_EXPR: return "IDENTIFIER_EXPR";
case AST_INT_LITERAL_EXPR: return "INT_LITERAL_EXPR";
case AST_FLOAT_LITERAL_EXPR: return "FLOAT_LITERAL_EXPR";
case AST_CHAR_LITERAL_EXPR: return "CHAR_LITERAL_EXPR";
case AST_STRING_LITERAL_EXPR: return "STRING_LITERAL_EXPR";
case AST_BINARY_OP_EXPR: return "BINARY_OP_EXPR";
case AST_UNARY_OP_EXPR: return "UNARY_OP_EXPR";
case AST_CALL_EXPR: return "CALL_EXPR";
case AST_CONDITIONAL_EXPR: return "CONDITIONAL_EXPR";
case AST_TYPE_SPECIFIER: return "TYPE_SPECIFIER";
case AST_ERROR_NODE: return "ERROR_NODE";
default: return "UNKNOWN_AST_NODE_TYPE";
}
}
/**
* @brief 递迴地打印AST树的结构,用于调试和可视化。
* 以缩进方式显示节点类型、位置和值,清晰展示树的层次结构。
* @param node 当前要打印的AST节点。
* @param indent 当前节点的缩进级别。
*/
void print_ast(ASTNode *node, int indent) {
if (node == NULL) {
return;
}
// 打印缩进
for (int i = 0; i < indent; ++i) {
printf(" ");
}
// 打印节点类型和位置
printf("[%s] (L%d C%d)", ast_node_type_to_string(node->type), node->line, node->col);
// 根据节点类型打印额外信息
switch (node->type) {
case AST_IDENTIFIER_EXPR:
printf(": %s", node->value.identifier_name);
break;
case AST_INT_LITERAL_EXPR:
printf(": %ld", node->value.int_val);
break;
case AST_FLOAT_LITERAL_EXPR:
printf(": %f", node->value.float_val);
break;
case AST_CHAR_LITERAL_EXPR:
printf(": '%c' (ASCII: %ld)", node->value.char_val, node->value.int_val);
break;
case AST_STRING_LITERAL_EXPR:
printf(": \"%s\"", node->value.string_val);
break;
case AST_BINARY_OP_EXPR:
case AST_UNARY_OP_EXPR:
printf(": %s", token_type_to_string(node->value.op_type));
break;
case AST_TYPE_SPECIFIER:
printf(": %s", token_type_to_string(node->value.type_specifier_token));
break;
// 其他节点类型无需额外值打印
default:
break;
}
printf("\n");
// 递归打印子节点,增加缩进级别
for (size_t i = 0; i < node->num_children; ++i) {
print_ast(node->children[i], indent + 1);
}
}
AST 代码量分析:
-
ast.h
文件大约 120 行。 -
ast.c
文件(包含所有辅助函数和注释)大约 220 行。 总计 AST 部分代码量约 340 行。
2.3 递归下降解析器的实现
递归下降解析是一种自顶向下的解析方法,其核心思想是为文法中的每一个非终结符编写一个对应的解析函数。这些函数会尝试匹配输入Token流中的相应模式,并递归调用其他解析函数来处理嵌套的语法结构。
2.3.1 parser.h
:语法分析器的接口定义
这个头文件声明了语法分析器的全局状态(current_token
)和所有解析函数。
// 文件名称: parser.h
// 语法分析器的接口定义,包括全局状态和所有解析函数声明。
#ifndef PARSER_H
#define PARSER_H
#include "lexer.h" // 引入词法分析器头文件,提供Token
#include "ast.h" // 引入AST头文件,用于构建语法树
#include <stddef.h> // for size_t
// --- 语法分析器全局状态变量 (为简化示例而使用) ---
// 指向当前正在处理的Token。在实际编译器中,这通常是Parser结构体的一个成员。
extern Token *current_token;
// --- 语法分析器核心辅助函数声明 ---
/**
* @brief 报告语法错误并终止程序。
* @param message 错误描述。
*/
void parser_error(const char *message);
/**
* @brief 前进到下一个Token,并释放当前Token的内存。
* 这是Parser与Lexer交互的主要方式。
*/
void advance();
/**
* @brief 检查当前Token的类型是否符合预期。
* 如果符合,则消耗该Token(调用advance())。
* 如果不符合,则报告语法错误并终止程序。
* @param expected_type 期望的Token类型。
*/
void expect(TokenType expected_type);
/**
* @brief 检查当前Token的类型是否符合预期,但不消耗它。
* 用于向前看(Lookahead),判断文法规则的选择分支。
* @param type 期望的Token类型。
* @return 如果匹配则返回true,否则返回false。
*/
bool match(TokenType type);
// --- 递归下降解析函数声明 (对应文法规则) ---
/**
* @brief 解析整个C语言程序,构建AST的根节点。
* 对应文法规则:Program -> FunctionDefinition* EOF
* @return 程序的根AST节点 (AST_PROGRAM)。
*/
ASTNode *parse_program();
/**
* @brief 解析函数定义。
* 对应文法规则:FunctionDefinition -> TypeSpecifier Identifier ( ParameterListOptional ) CompoundStatement
* @return 函数定义AST节点 (AST_FUNC_DEFINITION)。
*/
ASTNode *parse_function_definition();
/**
* @brief 解析类型规范符。
* 对应文法规则:TypeSpecifier -> INT_KEYWORD | VOID_KEYWORD | CHAR_KEYWORD | FLOAT_KEYWORD | DOUBLE_KEYWORD
* @return 类型规范符AST节点 (AST_TYPE_SPECIFIER)。
*/
ASTNode *parse_type_specifier();
/**
* @brief 解析参数列表(可选)。
* 对应文法规则:ParameterListOptional -> ParameterList | ε
* @param out_num_params 传出参数,返回实际解析到的参数数量。
* @return 包含参数声明AST节点的动态数组。调用者需负责free此数组。
*/
ASTNode **parse_parameter_list_optional(size_t *out_num_params);
/**
* @brief 解析参数列表。
* 对应文法规则:ParameterList -> TypeSpecifier Identifier (COMMA TypeSpecifier Identifier)*
* @param out_num_params 传出参数,返回实际解析到的参数数量。
* @return 包含参数声明AST节点的动态数组。调用者需负责free此数组。
*/
ASTNode **parse_parameter_list(size_t *out_num_params);
/**
* @brief 解析复合语句(代码块)。
* 对应文法规则:CompoundStatement -> LBRACE DeclarationListOptional StatementListOptional RBRACE
* @return 复合语句AST节点 (AST_COMPOUND_STMT)。
*/
ASTNode *parse_compound_statement();
/**
* @brief 解析声明列表(可选)。
* 对应文法规则:DeclarationListOptional -> Declaration*
* @param out_num_declarations 传出参数,返回实际解析到的声明数量。
* @return 包含变量声明AST节点的动态数组。调用者需负责free此数组。
*/
ASTNode **parse_declaration_list_optional(size_t *out_num_declarations);
/**
* @brief 解析单个声明。
* 对应文法规则:Declaration -> TypeSpecifier Identifier (ASSIGN Expression)? SEMICOLON
* @return 变量声明AST节点 (AST_VAR_DECLARATION)。
*/
ASTNode *parse_declaration();
/**
* @brief 解析语句列表(可选)。
* 对应文法规则:StatementListOptional -> Statement*
* @param out_num_statements 传出参数,返回实际解析到的语句数量。
* @return 包含语句AST节点的动态数组。调用者需负责free此数组。
*/
ASTNode **parse_statement_list_optional(size_t *out_num_statements);
/**
* @brief 解析单个语句。
* 对应文法规则:Statement -> ExpressionStatement | CompoundStatement | IfStatement | WhileStatement | ReturnStatement | BREAK_KEYWORD SEMICOLON | CONTINUE_KEYWORD SEMICOLON
* @return 语句AST节点。
*/
ASTNode *parse_statement();
/**
* @brief 解析表达式语句。
* 对应文法规则:ExpressionStatement -> Expression? SEMICOLON
* @return 表达式语句AST节点 (AST_EXPR_STMT)。
*/
ASTNode *parse_expression_statement();
/**
* @brief 解析If语句。
* 对应文法规则:IfStatement -> IF_KEYWORD LPAREN Expression RPAREN Statement (ELSE_KEYWORD Statement)?
* @return If语句AST节点 (AST_IF_STMT)。
*/
ASTNode *parse_if_statement();
/**
* @brief 解析While语句。
* 对应文法规则:WhileStatement -> WHILE_KEYWORD LPAREN Expression RPAREN Statement
* @return While语句AST节点 (AST_WHILE_STMT)。
*/
ASTNode *parse_while_statement();
/**
* @brief 解析Return语句。
* 对应文法规则:ReturnStatement -> RETURN_KEYWORD Expression? SEMICOLON
* @return Return语句AST节点 (AST_RETURN_STMT)。
*/
ASTNode *parse_return_statement();
/**
* @brief 解析表达式(入口,处理最低优先级操作符)。
* 对应文法规则:Expression -> AssignmentExpression
* @return 表达式AST节点。
*/
ASTNode *parse_expression();
/**
* @brief 解析赋值表达式。
* 对应文法规则:AssignmentExpression -> ConditionalExpression ( AssignmentOp ConditionalExpression )?
* @return 表达式AST节点。
*/
ASTNode *parse_assignment_expression();
/**
* @brief 解析条件表达式(三目运算符)。
* 对应文法规则:ConditionalExpression -> LogicalORExpression ( QUESTION Expression COLON ConditionalExpression )?
* @return 表达式AST节点。
*/
ASTNode *parse_conditional_expression();
/**
* @brief 解析逻辑或表达式。
* 对应文法规则:LogicalORExpression -> LogicalANDExpression ( LOGICAL_OR LogicalANDExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_logical_or_expression();
/**
* @brief 解析逻辑与表达式。
* 对应文法规则:LogicalANDExpression -> EqualityExpression ( LOGICAL_AND EqualityExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_logical_and_expression();
/**
* @brief 解析相等性表达式。
* 对应文法规则:EqualityExpression -> RelationalExpression ( (EQ | NE) RelationalExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_equality_expression();
/**
* @brief 解析关系表达式。
* 对应文法规则:RelationalExpression -> AdditiveExpression ( (LT | LE | GT | GE) AdditiveExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_relational_expression();
/**
* @brief 解析加减表达式。
* 对应文法规则:AdditiveExpression -> MultiplicativeExpression ( (PLUS | MINUS) MultiplicativeExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_additive_expression();
/**
* @brief 解析乘除模表达式。
* 对应文法规则:MultiplicativeExpression -> UnaryExpression ( (MULTIPLY | DIVIDE | MODULO) UnaryExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_multiplicative_expression();
/**
* @brief 解析一元表达式。
* 对应文法规则:UnaryExpression -> ( UnaryOp ) UnaryExpression | PrimaryExpression
* @return 表达式AST节点。
*/
ASTNode *parse_unary_expression();
/**
* @brief 解析主表达式(最高优先级)。
* 对应文法规则:PrimaryExpression -> IDENTIFIER (LPAREN ArgumentListOptional RPAREN)? | LITERAL | ( Expression )
* @return 表达式AST节点。
*/
ASTNode *parse_primary_expression();
/**
* @brief 解析函数调用表达式。
* @param func_name_token 函数名称的Token(IDENTIFIER类型)。
* @return 函数调用AST节点 (AST_CALL_EXPR)。
*/
ASTNode *parse_call_expression(Token *func_name_token);
/**
* @brief 解析函数调用的参数列表(可选)。
* @param out_num_args 传出参数,返回实际参数的数量。
* @return 包含参数表达式AST节点的动态数组。调用者需负责free此数组。
*/
ASTNode **parse_argument_list_optional(size_t *out_num_args);
/**
* @brief 解析函数调用的参数列表。
* @param out_num_args 传出参数,返回实际参数的数量。
* @return 包含参数表达式AST节点的动态数组。调用者需负责free此数组。
*/
ASTNode **parse_argument_list(size_t *out_num_args);
#endif // PARSER_H
2.3.2 parser.c
:语法分析器的具体实现
这个源文件包含了 parser.h
中所有解析函数的核心逻辑。每个函数都对应文法中的一个非终结符,通过调用其他解析函数和匹配Token来构建AST。
// 文件名称: parser.c
// 语法分析器的具体实现,采用递归下降解析,将Token流转换为抽象语法树 (AST)。
#include "parser.h" // 引入语法分析器头文件
#include <stdio.h> // for fprintf
#include <stdlib.h> // for exit, realloc
// --- 语法分析器全局状态变量定义 ---
Token *current_token; // 指向词法分析器提供的当前Token
// --- 语法分析器错误处理 ---
/**
* @brief 报告语法错误并终止程序。
* 打印出错误信息和当前Token的位置及内容,方便调试。
* 在实际生产级编译器中,会尝试更复杂的错误恢复而非直接退出。
* @param message 错误描述。
*/
void parser_error(const char *message) {
fprintf(stderr, "Parser Error at line %d, column %d: %s (near '%s')\n",
current_token->line, current_token->col, message,
current_token->lexeme ? current_token->lexeme : "EOF");
exit(EXIT_FAILURE); // 简化的错误处理:直接退出
}
// --- 语法分析器核心辅助函数 ---
/**
* @brief 前进到下一个Token,并释放当前Token的内存。
* 这是Parser与Lexer交互的唯一方式。
*/
void advance() {
if (current_token != NULL) {
free_token(current_token); // 释放旧Token的内存
}
current_token = get_next_token(); // 从词法分析器获取下一个Token
// 可在此处添加调试输出,查看当前处理的Token
// printf(" [Parser] Advanced to: %s ('%s') L%d C%d\n",
// token_type_to_string(current_token->type),
// current_token->lexeme ? current_token->lexeme : "null",
// current_token->line, current_token->col);
}
/**
* @brief 检查当前Token的类型是否符合预期。
* 如果符合,则消耗该Token。否则,报告语法错误。
* @param expected_type 期望的Token类型。
*/
void expect(TokenType expected_type) {
if (current_token->type == expected_type) {
advance();
} else {
char msg[256];
sprintf(msg, "Expected '%s' but found '%s'",
token_type_to_string(expected_type),
token_type_to_string(current_token->type));
parser_error(msg);
}
}
/**
* @brief 检查当前Token的类型是否符合预期,但不消耗它。
* @param type 期望的Token类型。
* @return 如果匹配则返回true,否则返回false。
*/
bool match(TokenType type) {
return current_token->type == type;
}
// --- 递归下降解析函数实现 (对应文法规则) ---
/**
* @brief 解析整个C语言程序,构建AST的根节点。
* 文法:Program -> FunctionDefinition* EOF
* @return 程序的根AST节点 (AST_PROGRAM)。
*/
ASTNode *parse_program() {
ASTNode *program_node = create_ast_node(AST_PROGRAM, current_token->line, current_token->col);
// 程序由一个或多个函数定义组成
while (current_token->type != TOKEN_EOF) {
ASTNode *func_def = parse_function_definition();
add_child(program_node, func_def);
}
expect(TOKEN_EOF); // 确保文件结束
return program_node;
}
/**
* @brief 解析函数定义。
* 文法:FunctionDefinition -> TypeSpecifier Identifier ( ParameterListOptional ) CompoundStatement
* @return 函数定义AST节点 (AST_FUNC_DEFINITION)。
*/
ASTNode *parse_function_definition() {
int line = current_token->line;
int col = current_token->col;
ASTNode *func_def_node = create_ast_node(AST_FUNC_DEFINITION, line, col);
// 1. 解析返回类型
ASTNode *type_specifier = parse_type_specifier();
add_child(func_def_node, type_specifier);
// 2. 解析函数名称 (标识符)
if (!match(TOKEN_IDENTIFIER)) {
parser_error("Expected function name (identifier) in function definition.");
}
ASTNode *func_name_node = create_ast_node(AST_IDENTIFIER_EXPR, current_token->line, current_token->col);
set_node_string_value(func_name_node, current_token->lexeme);
add_child(func_def_node, func_name_node);
advance(); // 消耗函数名称
// 3. 匹配左括号 '('
expect(TOKEN_DELIM_LPAREN);
// 4. 解析参数列表 (可选)
size_t num_params = 0;
ASTNode **param_nodes = parse_parameter_list_optional(&num_params);
for (size_t i = 0; i < num_params; ++i) {
add_child(func_def_node, param_nodes[i]);
}
if (param_nodes != NULL) free(param_nodes); // 释放参数节点指针数组
// 5. 匹配右括号 ')'
expect(TOKEN_DELIM_RPAREN);
// 6. 解析函数体 (复合语句)
ASTNode *body_node = parse_compound_statement();
add_child(func_def_node, body_node);
return func_def_node;
}
/**
* @brief 解析类型规范符。
* 文法:TypeSpecifier -> INT_KEYWORD | VOID_KEYWORD | ...
* @return 类型规范符AST节点 (AST_TYPE_SPECIFIER)。
*/
ASTNode *parse_type_specifier() {
int line = current_token->line;
int col = current_token->col;
ASTNode *type_node = create_ast_node(AST_TYPE_SPECIFIER, line, col);
if (match(TOKEN_KEYWORD_INT)) {
set_node_type_specifier(type_node, TOKEN_KEYWORD_INT);
advance();
} else if (match(TOKEN_KEYWORD_VOID)) {
set_node_type_specifier(type_node, TOKEN_KEYWORD_VOID);
advance();
} else if (match(TOKEN_KEYWORD_CHAR)) {
set_node_type_specifier(type_node, TOKEN_KEYWORD_CHAR);
advance();
} else if (match(TOKEN_KEYWORD_FLOAT)) {
set_node_type_specifier(type_node, TOKEN_KEYWORD_FLOAT);
advance();
} else if (match(TOKEN_KEYWORD_DOUBLE)) {
set_node_type_specifier(type_node, TOKEN_KEYWORD_DOUBLE);
advance();
} else {
parser_error("Expected a type specifier (int, void, char, float, double).");
}
return type_node;
}
/**
* @brief 解析参数列表(可选)。
* 文法:ParameterListOptional -> ParameterList | ε
* @param out_num_params 传出参数,返回实际解析到的参数数量。
* @return 包含参数声明AST节点的动态数组。
*/
ASTNode **parse_parameter_list_optional(size_t *out_num_params) {
if (match(TOKEN_DELIM_RPAREN)) { // 空参数列表
*out_num_params = 0;
return NULL;
}
return parse_parameter_list(out_num_params);
}
/**
* @brief 解析参数列表。
* 文法:ParameterList -> TypeSpecifier Identifier (COMMA TypeSpecifier Identifier)*
* @param out_num_params 传出参数,返回实际解析到的参数数量。
* @return 包含参数声明AST节点的动态数组。
*/
ASTNode **parse_parameter_list(size_t *out_num_params) {
ASTNode **param_nodes = NULL;
size_t capacity = INITIAL_CHILDREN_CAPACITY; // 初始容量
*out_num_params = 0; // 初始数量
param_nodes = (ASTNode**)malloc(sizeof(ASTNode*) * capacity);
if (param_nodes == NULL) {
perror("Failed to allocate memory for parameter list");
exit(EXIT_FAILURE);
}
// 至少一个参数
ASTNode *type_node = parse_type_specifier();
if (!match(TOKEN_IDENTIFIER)) {
parser_error("Expected parameter name (identifier).");
}
ASTNode *param_name_node = create_ast_node(AST_IDENTIFIER_EXPR, current_token->line, current_token->col);
set_node_string_value(param_name_node, current_token->lexeme);
advance(); // 消耗参数名称
ASTNode *param_decl_node = create_ast_node(AST_PARAM_DECLARATION, type_node->line, type_node->col);
add_child(param_decl_node, type_node);
add_child(param_decl_node, param_name_node);
param_nodes[(*out_num_params)++] = param_decl_node;
// 循环处理额外参数
while (match(TOKEN_OP_COMMA)) {
expect(TOKEN_OP_COMMA); // 消耗 ','
// 扩容
if (*out_num_params == capacity) {
capacity *= CHILDREN_CAPACITY_FACTOR;
param_nodes = (ASTNode**)realloc(param_nodes, sizeof(ASTNode*) * capacity);
if (param_nodes == NULL) {
perror("Failed to reallocate memory for parameter list");
exit(EXIT_FAILURE);
}
}
type_node = parse_type_specifier();
if (!match(TOKEN_IDENTIFIER)) {
parser_error("Expected parameter name (identifier).");
}
param_name_node = create_ast_node(AST_IDENTIFIER_EXPR, current_token->line, current_token->col);
set_node_string_value(param_name_node, current_token->lexeme);
param_decl_node = create_ast_node(AST_PARAM_DECLARATION, type_node->line, type_node->col); // 重新创建
add_child(param_decl_node, type_node);
add_child(param_decl_node, param_name_node);
param_nodes[(*out_num_params)++] = param_decl_node;
advance(); // 消耗参数名称
}
return param_nodes;
}
/**
* @brief 解析复合语句(代码块)。
* 文法:CompoundStatement -> LBRACE DeclarationListOptional StatementListOptional RBRACE
* @return 复合语句AST节点 (AST_COMPOUND_STMT)。
*/
ASTNode *parse_compound_statement() {
int line = current_token->line;
int col = current_token->col;
ASTNode *compound_stmt_node = create_ast_node(AST_COMPOUND_STMT, line, col);
expect(TOKEN_DELIM_LBRACE); // 匹配 '{'
// 解析声明列表 (可选)
size_t num_declarations = 0;
ASTNode **decl_nodes = parse_declaration_list_optional(&num_declarations);
for (size_t i = 0; i < num_declarations; ++i) {
add_child(compound_stmt_node, decl_nodes[i]);
}
if (decl_nodes != NULL) free(decl_nodes);
// 解析语句列表 (可选)
size_t num_statements = 0;
ASTNode **stmt_nodes = parse_statement_list_optional(&num_statements);
for (size_t i = 0; i < num_statements; ++i) {
add_child(compound_stmt_node, stmt_nodes[i]);
}
if (stmt_nodes != NULL) free(stmt_nodes);
expect(TOKEN_DELIM_RBRACE); // 匹配 '}'
return compound_stmt_node;
}
/**
* @brief 解析声明列表(可选)。
* 文法:DeclarationListOptional -> Declaration*
* @param out_num_declarations 传出参数,返回实际解析到的声明数量。
* @return 包含变量声明AST节点的动态数组。
*/
ASTNode **parse_declaration_list_optional(size_t *out_num_declarations) {
ASTNode **decl_nodes = NULL;
size_t capacity = INITIAL_CHILDREN_CAPACITY;
*out_num_declarations = 0;
// 声明以类型关键字开头
while (match(TOKEN_KEYWORD_INT) || match(TOKEN_KEYWORD_VOID) ||
match(TOKEN_KEYWORD_CHAR) || match(TOKEN_KEYWORD_FLOAT) || match(TOKEN_KEYWORD_DOUBLE)) {
if (*out_num_declarations == 0) { // 第一次分配
decl_nodes = (ASTNode**)malloc(sizeof(ASTNode*) * capacity);
if (decl_nodes == NULL) {
perror("Failed to allocate memory for declaration list");
exit(EXIT_FAILURE);
}
} else if (*out_num_declarations == capacity) { // 扩容
capacity *= CHILDREN_CAPACITY_FACTOR;
decl_nodes = (ASTNode**)realloc(decl_nodes, sizeof(ASTNode*) * capacity);
if (decl_nodes == NULL) {
perror("Failed to reallocate memory for declaration list");
exit(EXIT_FAILURE);
}
}
decl_nodes[(*out_num_declarations)++] = parse_declaration();
}
return decl_nodes;
}
/**
* @brief 解析单个声明。
* 文法:Declaration -> TypeSpecifier Identifier (ASSIGN Expression)? SEMICOLON
* @return 变量声明AST节点 (AST_VAR_DECLARATION)。
*/
ASTNode *parse_declaration() {
int line = current_token->line;
int col = current_token->col;
ASTNode *decl_node = create_ast_node(AST_VAR_DECLARATION, line, col);
// 1. 解析类型规范符
ASTNode *type_node = parse_type_specifier();
add_child(decl_node, type_node);
// 2. 解析变量名称 (标识符)
if (!match(TOKEN_IDENTIFIER)) {
parser_error("Expected variable name (identifier) in declaration.");
}
ASTNode *var_name_node = create_ast_node(AST_IDENTIFIER_EXPR, current_token->line, current_token->col);
set_node_string_value(var_name_node, current_token->lexeme);
add_child(decl_node, var_name_node);
advance(); // 消耗变量名称
// 3. 解析可选的初始化赋值
if (match(TOKEN_OP_ASSIGN)) {
expect(TOKEN_OP_ASSIGN); // 匹配 '='
ASTNode *init_expr = parse_expression(); // 解析初始化表达式
add_child(decl_node, init_expr);
}
// 4. 匹配分号 ';'
expect(TOKEN_DELIM_SEMICOLON);
return decl_node;
}
/**
* @brief 解析语句列表(可选)。
* 文法:StatementListOptional -> Statement*
* @param out_num_statements 传出参数,返回实际解析到的语句数量。
* @return 包含语句AST节点的动态数组。
*/
ASTNode **parse_statement_list_optional(size_t *out_num_statements) {
ASTNode **stmt_nodes = NULL;
size_t capacity = INITIAL_CHILDREN_CAPACITY;
*out_num_statements = 0;
// 语句的起始Token可能有很多种
while (match(TOKEN_IDENTIFIER) || match(TOKEN_DELIM_LBRACE) ||
match(TOKEN_KEYWORD_IF) || match(TOKEN_KEYWORD_WHILE) ||
match(TOKEN_KEYWORD_RETURN) || match(TOKEN_DELIM_SEMICOLON) || // 空表达式语句
match(TOKEN_LITERAL_INT) || match(TOKEN_LITERAL_FLOAT) ||
match(TOKEN_LITERAL_CHAR) || match(TOKEN_LITERAL_STRING) ||
match(TOKEN_DELIM_LPAREN) || // (Expression)
match(TOKEN_OP_PLUS) || match(TOKEN_OP_MINUS) ||
match(TOKEN_OP_INCREMENT) || match(TOKEN_OP_DECREMENT) ||
match(TOKEN_OP_NOT_LOGICAL) || match(TOKEN_OP_BITWISE_NOT) ||
match(TOKEN_KEYWORD_BREAK) || match(TOKEN_KEYWORD_CONTINUE)
) {
if (*out_num_statements == 0) { // 第一次分配
stmt_nodes = (ASTNode**)malloc(sizeof(ASTNode*) * capacity);
if (stmt_nodes == NULL) {
perror("Failed to allocate memory for statement list");
exit(EXIT_FAILURE);
}
} else if (*out_num_statements == capacity) { // 扩容
capacity *= CHILDREN_CAPACITY_FACTOR;
stmt_nodes = (ASTNode**)realloc(stmt_nodes, sizeof(ASTNode*) * capacity);
if (stmt_nodes == NULL) {
perror("Failed to reallocate memory for statement list");
exit(EXIT_FAILURE);
}
}
stmt_nodes[(*out_num_statements)++] = parse_statement();
}
return stmt_nodes;
}
/**
* @brief 解析单个语句。
* 文法:Statement -> ExpressionStatement | CompoundStatement | IfStatement | WhileStatement | ReturnStatement | BREAK_KEYWORD SEMICOLON | CONTINUE_KEYWORD SEMICOLON
* 根据当前Token的类型判断是哪种语句。
* @return 语句AST节点。
*/
ASTNode *parse_statement() {
if (match(TOKEN_DELIM_LBRACE)) { // { ... } 复合语句
return parse_compound_statement();
} else if (match(TOKEN_KEYWORD_IF)) { // if (...) ...
return parse_if_statement();
} else if (match(TOKEN_KEYWORD_WHILE)) { // while (...) ...
return parse_while_statement();
} else if (match(TOKEN_KEYWORD_RETURN)) { // return ...;
return parse_return_statement();
} else if (match(TOKEN_KEYWORD_BREAK)) { // break;
int line = current_token->line;
int col = current_token->col;
advance(); // 消耗 'break'
expect(TOKEN_DELIM_SEMICOLON); // 消耗 ';'
return create_ast_node(AST_BREAK_STMT, line, col);
} else if (match(TOKEN_KEYWORD_CONTINUE)) { // continue;
int line = current_token->line;
int col = current_token->col;
advance(); // 消耗 'continue'
expect(TOKEN_DELIM_SEMICOLON); // 消耗 ';'
return create_ast_node(AST_CONTINUE_STMT, line, col);
} else { // 默认为表达式语句 (也包括空语句,即只有分号)
return parse_expression_statement();
}
}
/**
* @brief 解析表达式语句。
* 文法:ExpressionStatement -> Expression? SEMICOLON
* @return 表达式语句AST节点 (AST_EXPR_STMT)。
*/
ASTNode *parse_expression_statement() {
int line = current_token->line;
int col = current_token->col;
ASTNode *expr_stmt_node = create_ast_node(AST_EXPR_STMT, line, col);
// 表达式是可选的,如果当前Token不是分号,则尝试解析表达式
if (!match(TOKEN_DELIM_SEMICOLON)) {
ASTNode *expr = parse_expression();
add_child(expr_stmt_node, expr);
}
expect(TOKEN_DELIM_SEMICOLON); // 匹配 ';'
return expr_stmt_node;
}
/**
* @brief 解析If语句。
* 文法:IfStatement -> IF_KEYWORD LPAREN Expression RPAREN Statement (ELSE_KEYWORD Statement)?
* @return If语句AST节点 (AST_IF_STMT)。
*/
ASTNode *parse_if_statement() {
int line = current_token->line;
int col = current_token->col;
ASTNode *if_stmt_node = create_ast_node(AST_IF_STMT, line, col);
expect(TOKEN_KEYWORD_IF);
expect(TOKEN_DELIM_LPAREN);
ASTNode *condition = parse_expression();
add_child(if_stmt_node, condition);
expect(TOKEN_DELIM_RPAREN);
ASTNode *then_branch = parse_statement();
add_child(if_stmt_node, then_branch);
// 检查是否有else分支
if (match(TOKEN_KEYWORD_ELSE)) {
expect(TOKEN_KEYWORD_ELSE);
ASTNode *else_branch = parse_statement();
add_child(if_stmt_node, else_branch);
}
return if_stmt_node;
}
/**
* @brief 解析While语句。
* 文法:WhileStatement -> WHILE_KEYWORD LPAREN Expression RPAREN Statement
* @return While语句AST节点 (AST_WHILE_STMT)。
*/
ASTNode *parse_while_statement() {
int line = current_token->line;
int col = current_token->col;
ASTNode *while_stmt_node = create_ast_node(AST_WHILE_STMT, line, col);
expect(TOKEN_KEYWORD_WHILE);
expect(TOKEN_DELIM_LPAREN);
ASTNode *condition = parse_expression();
add_child(while_stmt_node, condition);
expect(TOKEN_DELIM_RPAREN);
ASTNode *body = parse_statement();
add_child(while_stmt_node, body);
return while_stmt_node;
}
/**
* @brief 解析Return语句。
* 文法:ReturnStatement -> RETURN_KEYWORD Expression? SEMICOLON
* @return Return语句AST节点 (AST_RETURN_STMT)。
*/
ASTNode *parse_return_statement() {
int line = current_token->line;
int col = current_token->col;
ASTNode *return_stmt_node = create_ast_node(AST_RETURN_STMT, line, col);
expect(TOKEN_KEYWORD_RETURN);
// 返回值表达式是可选的
if (!match(TOKEN_DELIM_SEMICOLON)) {
ASTNode *expr = parse_expression();
add_child(return_stmt_node, expr);
}
expect(TOKEN_DELIM_SEMICOLON);
return return_stmt_node;
}
// --- 表达式解析 (优先级从低到高,递归下降实现) ---
/**
* @brief 解析表达式(入口,处理最低优先级操作符)。
* 文法:Expression -> AssignmentExpression
* @return 表达式AST节点。
*/
ASTNode *parse_expression() {
return parse_assignment_expression();
}
/**
* @brief 解析赋值表达式。
* 文法:AssignmentExpression -> ConditionalExpression ( AssignmentOp ConditionalExpression )?
* 赋值操作符是右结合的。
* @return 表达式AST节点。
*/
ASTNode *parse_assignment_expression() {
ASTNode *left_expr = parse_conditional_expression(); // 先解析左侧的条件表达式
// 检查是否是赋值操作符
if (match(TOKEN_OP_ASSIGN) || match(TOKEN_OP_ASSIGN_PLUS) ||
match(TOKEN_OP_ASSIGN_MINUS) || match(TOKEN_OP_ASSIGN_MULTIPLY) ||
match(TOKEN_OP_ASSIGN_DIVIDE) || match(TOKEN_OP_ASSIGN_MODULO) ||
match(TOKEN_OP_ASSIGN_AND_BITWISE) || match(TOKEN_OP_ASSIGN_OR_BITWISE) ||
match(TOKEN_OP_ASSIGN_XOR_BITWISE) || match(TOKEN_OP_ASSIGN_LSHIFT) ||
match(TOKEN_OP_ASSIGN_RSHIFT)) {
int line = current_token->line;
int col = current_token->col;
TokenType op_type = current_token->type; // 记录操作符类型
advance(); // 消耗赋值操作符
ASTNode *right_expr = parse_assignment_expression(); // 递归解析右侧的赋值表达式 (右结合性)
ASTNode *assign_op_node = create_ast_node(AST_BINARY_OP_EXPR, line, col);
set_node_op_type(assign_op_node, op_type);
add_child(assign_op_node, left_expr);
add_child(assign_op_node, right_expr);
return assign_op_node;
}
return left_expr; // 如果没有赋值操作符,就返回解析到的左侧表达式
}
/**
* @brief 解析条件表达式(三目运算符)。
* 文法:ConditionalExpression -> LogicalORExpression ( QUESTION Expression COLON ConditionalExpression )?
* @return 表达式AST节点。
*/
ASTNode *parse_conditional_expression() {
ASTNode *condition_expr = parse_logical_or_expression();
if (match(TOKEN_OP_QUESTION)) {
int line = current_token->line;
int col = current_token->col;
advance(); // 消耗 '?'
ASTNode *true_branch_expr = parse_expression(); // 解析真值表达式 (Expression)
expect(TOKEN_OP_COLON); // 匹配 ':'
ASTNode *false_branch_expr = parse_conditional_expression(); // 递归解析假值表达式 (ConditionalExpression)
ASTNode *conditional_node = create_ast_node(AST_CONDITIONAL_EXPR, line, col);
add_child(conditional_node, condition_expr);
add_child(conditional_node, true_branch_expr);
add_child(conditional_node, false_branch_expr);
return conditional_node;
}
return condition_expr;
}
/**
* @brief 解析逻辑或表达式。
* 文法:LogicalORExpression -> LogicalANDExpression ( LOGICAL_OR LogicalANDExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_logical_or_expression() {
ASTNode *left_expr = parse_logical_and_expression();
while (match(TOKEN_OP_OR_LOGICAL)) {
int line = current_token->line;
int col = current_token->col;
TokenType op_type = current_token->type;
advance(); // 消耗 '||'
ASTNode *right_expr = parse_logical_and_expression();
ASTNode *bin_op_node = create_ast_node(AST_BINARY_OP_EXPR, line, col);
set_node_op_type(bin_op_node, op_type);
add_child(bin_op_node, left_expr);
add_child(bin_op_node, right_expr);
left_expr = bin_op_node; // 左结合性
}
return left_expr;
}
/**
* @brief 解析逻辑与表达式。
* 文法:LogicalANDExpression -> EqualityExpression ( LOGICAL_AND EqualityExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_logical_and_expression() {
ASTNode *left_expr = parse_equality_expression();
while (match(TOKEN_OP_AND_LOGICAL)) {
int line = current_token->line;
int col = current_token->col;
TokenType op_type = current_token->type;
advance(); // 消耗 '&&'
ASTNode *right_expr = parse_equality_expression();
ASTNode *bin_op_node = create_ast_node(AST_BINARY_OP_EXPR, line, col);
set_node_op_type(bin_op_node, op_type);
add_child(bin_op_node, left_expr);
add_child(bin_op_node, right_expr);
left_expr = bin_op_node; // 左结合性
}
return left_expr;
}
/**
* @brief 解析相等性表达式。
* 文法:EqualityExpression -> RelationalExpression ( (EQ | NE) RelationalExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_equality_expression() {
ASTNode *left_expr = parse_relational_expression();
while (match(TOKEN_OP_EQ) || match(TOKEN_OP_NE)) {
int line = current_token->line;
int col = current_token->col;
TokenType op_type = current_token->type;
advance(); // 消耗 '==' 或 '!='
ASTNode *right_expr = parse_relational_expression();
ASTNode *bin_op_node = create_ast_node(AST_BINARY_OP_EXPR, line, col);
set_node_op_type(bin_op_node, op_type);
add_child(bin_op_node, left_expr);
add_child(bin_op_node, right_expr);
left_expr = bin_op_node; // 左结合性
}
return left_expr;
}
/**
* @brief 解析关系表达式。
* 文法:RelationalExpression -> AdditiveExpression ( (LT | LE | GT | GE) AdditiveExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_relational_expression() {
ASTNode *left_expr = parse_additive_expression();
while (match(TOKEN_OP_LT) || match(TOKEN_OP_LE) ||
match(TOKEN_OP_GT) || match(TOKEN_OP_GE)) {
int line = current_token->line;
int col = current_token->col;
TokenType op_type = current_token->type;
advance(); // 消耗 '<', '<=', '>', '>='
ASTNode *right_expr = parse_additive_expression();
ASTNode *bin_op_node = create_ast_node(AST_BINARY_OP_EXPR, line, col);
set_node_op_type(bin_op_node, op_type);
add_child(bin_op_node, left_expr);
add_child(bin_op_node, right_expr);
left_expr = bin_op_node; // 左结合性
}
return left_expr;
}
/**
* @brief 解析加减表达式。
* 文法:AdditiveExpression -> MultiplicativeExpression ( (PLUS | MINUS) MultiplicativeExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_additive_expression() {
ASTNode *left_expr = parse_multiplicative_expression();
while (match(TOKEN_OP_PLUS) || match(TOKEN_OP_MINUS)) {
int line = current_token->line;
int col = current_token->col;
TokenType op_type = current_token->type;
advance(); // 消耗 '+' 或 '-'
ASTNode *right_expr = parse_multiplicative_expression();
ASTNode *bin_op_node = create_ast_node(AST_BINARY_OP_EXPR, line, col);
set_node_op_type(bin_op_node, op_type);
add_child(bin_op_node, left_expr);
add_child(bin_op_node, right_expr);
left_expr = bin_op_node; // 左结合性
}
return left_expr;
}
/**
* @brief 解析乘除模表达式。
* 文法:MultiplicativeExpression -> UnaryExpression ( (MULTIPLY | DIVIDE | MODULO) UnaryExpression )*
* @return 表达式AST节点。
*/
ASTNode *parse_multiplicative_expression() {
ASTNode *left_expr = parse_unary_expression();
while (match(TOKEN_OP_MULTIPLY) || match(TOKEN_OP_DIVIDE) || match(TOKEN_OP_MODULO)) {
int line = current_token->line;
int col = current_token->col;
TokenType op_type = current_token->type;
advance(); // 消耗 '*', '/', '%'
ASTNode *right_expr = parse_unary_expression();
ASTNode *bin_op_node = create_ast_node(AST_BINARY_OP_EXPR, line, col);
set_node_op_type(bin_op_node, op_type);
add_child(bin_op_node, left_expr);
add_child(bin_op_node, right_expr);
left_expr = bin_op_node; // 左结合性
}
return left_expr;
}
/**
* @brief 解析一元表达式。
* 文法:UnaryExpression -> ( UnaryOp ) UnaryExpression | PrimaryExpression
* 一元操作符是右结合的。
* @return 表达式AST节点。
*/
ASTNode *parse_unary_expression() {
int line = current_token->line;
int col = current_token->col;
// 检查是否是一元操作符
if (match(TOKEN_OP_PLUS) || match(TOKEN_OP_MINUS) ||
match(TOKEN_OP_INCREMENT) || match(TOKEN_OP_DECREMENT) ||
match(TOKEN_OP_NOT_LOGICAL) || match(TOKEN_OP_BITWISE_NOT)) {
TokenType op_type = current_token->type;
advance(); // 消耗一元操作符
ASTNode *operand = parse_unary_expression(); // 递归解析操作数 (右结合性)
ASTNode *unary_op_node = create_ast_node(AST_UNARY_OP_EXPR, line, col);
set_node_op_type(unary_op_node, op_type);
add_child(unary_op_node, operand);
return unary_op_node;
}
// 如果没有一元操作符,就解析主表达式
return parse_primary_expression();
}
/**
* @brief 解析主表达式(最高优先级)。
* 文法:PrimaryExpression -> IDENTIFIER (LPAREN ArgumentListOptional RPAREN)? | LITERAL | ( Expression )
* @return 表达式AST节点。
*/
ASTNode *parse_primary_expression() {
int line = current_token->line;
int col = current_token->col;
ASTNode *node = NULL;
switch (current_token->type) {
case TOKEN_IDENTIFIER: { // 标识符,可能是变量或函数调用
Token *id_token = current_token;
advance(); // 消耗标识符
// 检查是否是函数调用 (标识符后紧跟 '(' )
if (match(TOKEN_DELIM_LPAREN)) {
return parse_call_expression(id_token); // 解析函数调用
} else {
// 否则就是一个普通的标识符表达式 (变量)
node = create_ast_node(AST_IDENTIFIER_EXPR, id_token->line, id_token->col);
set_node_string_value(node, id_token->lexeme);
// 注意:id_token 的内存已由 advance() 释放,lexeme 已被复制到 AST 节点中
return node;
}
}
case TOKEN_LITERAL_INT:
node = create_ast_node(AST_INT_LITERAL_EXPR, line, col);
set_node_int_value(node, current_token->value.int_val);
advance();
return node;
case TOKEN_LITERAL_FLOAT:
node = create_ast_node(AST_FLOAT_LITERAL_EXPR, line, col);
set_node_float_value(node, current_token->value.float_val);
advance();
return node;
case TOKEN_LITERAL_CHAR:
node = create_ast_node(AST_CHAR_LITERAL_EXPR, line, col);
set_node_char_value(node, (char)current_token->value.int_val);
advance();
return node;
case TOKEN_LITERAL_STRING:
node = create_ast_node(AST_STRING_LITERAL_EXPR, line, col);
set_node_string_value(node, current_token->lexeme);
advance();
return node;
case TOKEN_DELIM_LPAREN: // 括号表达式 ( Expression )
expect(TOKEN_DELIM_LPAREN);
node = parse_expression(); // 递归解析括号内的表达式
expect(TOKEN_DELIM_RPAREN);
return node;
default:
parser_error("Expected a primary expression (identifier, literal, or '(').");
return NULL; // 错误处理后返回 NULL
}
}
/**
* @brief 解析函数调用表达式。
* 文法:CallExpression -> Identifier ( ArgumentListOptional )
* @param func_name_token 函数名称的Token(IDENTIFIER类型,已消耗)。
* @return 函数调用AST节点 (AST_CALL_EXPR)。
*/
ASTNode *parse_call_expression(Token *func_name_token) {
// 函数名Token已由parse_primary_expression消耗,但我们需要其信息来创建节点
int line = func_name_token->line;
int col = func_name_token->col;
ASTNode *call_expr_node = create_ast_node(AST_CALL_EXPR, line, col);
// 函数名称作为第一个子节点
ASTNode *func_id_node = create_ast_node(AST_IDENTIFIER_EXPR, func_name_token->line, func_name_token->col);
set_node_string_value(func_id_node, func_name_token->lexeme);
add_child(call_expr_node, func_id_node);
// 匹配左括号 (已经在parse_primary_expression中判断,这里直接expect)
expect(TOKEN_DELIM_LPAREN);
// 解析参数列表 (可选)
size_t num_args = 0;
ASTNode **arg_nodes = parse_argument_list_optional(&num_args);
for (size_t i = 0; i < num_args; ++i) {
add_child(call_expr_node, arg_nodes[i]);
}
if (arg_nodes != NULL) free(arg_nodes); // 释放存放参数节点指针的数组
// 匹配右括号
expect(TOKEN_DELIM_RPAREN);
return call_expr_node;
}
/**
* @brief 解析函数调用的参数列表(可选)。
* 文法:ArgumentListOptional -> ArgumentList | ε
* @param out_num_args 传出参数,返回实际参数的数量。
* @return 包含参数表达式AST节点的动态数组。
*/
ASTNode **parse_argument_list_optional(size_t *out_num_args) {
if (match(TOKEN_DELIM_RPAREN)) { // 空参数列表
*out_num_args = 0;
return NULL;
}
return parse_argument_list(out_num_args);
}
/**
* @brief 解析函数调用的参数列表。
* 文法:ArgumentList -> Expression (COMMA Expression)*
* @param out_num_args 传出参数,返回实际参数的数量。
* @return 包含参数表达式AST节点的动态数组。
*/
ASTNode **parse_argument_list(size_t *out_num_args) {
ASTNode **arg_nodes = NULL;
size_t capacity = INITIAL_CHILDREN_CAPACITY;
*out_num_args = 0;
arg_nodes = (ASTNode**)malloc(sizeof(ASTNode*) * capacity);
if (arg_nodes == NULL) {
perror("Failed to allocate memory for argument list");
exit(EXIT_FAILURE);
}
// 至少一个参数
arg_nodes[(*out_num_args)++] = parse_expression();
// 循环处理额外参数
while (match(TOKEN_OP_COMMA)) {
expect(TOKEN_OP_COMMA); // 消耗 ','
if (*out_num_args == capacity) {
capacity *= CHILDREN_CAPACITY_FACTOR;
arg_nodes = (ASTNode**)realloc(arg_nodes, sizeof(ASTNode*) * capacity);
if (arg_nodes == NULL) {
perror("Failed to reallocate memory for argument list");
exit(EXIT_FAILURE);
}
}
arg_nodes[(*out_num_args)++] = parse_expression();
}
return arg_nodes;
}
语法分析代码量分析:
-
parser.h
文件大约 170 行。 -
parser.c
文件(包含所有解析函数和注释)大约 700 行。 总计语法分析部分代码量约 870 行。
2.4 编译前端的总量与集成
结合词法分析和AST部分的实现,我们目前的代码量大致如下:
-
lexer.h
: 130行 -
lexer.c
: 600行 -
ast.h
: 120行 -
ast.c
: 220行 -
parser.h
: 170行 -
parser.c
: 700行
总代码量已达到 1940 行,符合您要求的 1-2k 行代码演示。这些代码共同构成了我们简易C语言编译器的前端。
3. 前端代码整合与完整示例
现在,我们将把所有模块整合起来,并使用一个 main.c
文件来驱动整个编译前端的流程:初始化词法分析器,启动语法分析,然后打印出生成的AST。
3.1 main.c
:编译前端的驱动程序
// 文件名称: main.c
// C语言编译器前端的驱动程序,整合词法分析器和语法分析器。
#include "lexer.h" // 引入词法分析器
#include "parser.h" // 引入语法分析器
#include "ast.h" // 引入AST定义
// 声明parser.c中使用的全局current_token,以便main函数可以访问
extern Token *current_token;
/**
* @brief 编译器的主要入口函数。
* 接收源代码文件路径作为命令行参数,执行词法分析和语法分析,
* 并打印生成的抽象语法树。最后释放所有资源。
* @param argc 命令行参数数量。
* @param argv 命令行参数数组。
* @return 程序的退出状态码。
*/
int main(int argc, char *argv[]) {
// 检查命令行参数数量
if (argc != 2) {
fprintf(stderr, "Usage: %s <source_file.c>\n", argv[0]);
return EXIT_FAILURE;
}
printf("--- Starting C Compiler Frontend ---\n");
printf("Source File: %s\n\n", argv[1]);
// 1. 初始化词法分析器
init_lexer(argv[1]);
// 2. 获取第一个Token,启动语法分析器
// current_token必须在parser.c的advance()中被初始化,所以这里调用一次advance()
advance();
printf("--- Lexical Analysis & Parsing Started ---\n");
// 3. 开始语法分析,生成抽象语法树
ASTNode *program_ast_root = NULL;
// 使用try-catch机制 (C语言中通过setjmp/longjmp模拟) 可以实现更优雅的错误恢复
// 但为简化,这里遇到错误直接由parser_error退出
program_ast_root = parse_program();
printf("\n--- Syntax Analysis Completed ---\n");
printf("Generated Abstract Syntax Tree:\n");
printf("----------------------------------\n");
// 4. 打印生成的AST,用于调试和可视化
print_ast(program_ast_root, 0);
printf("----------------------------------\n");
printf("\n--- Freeing AST Memory ---\n");
// 5. 释放AST占用的所有内存
free_ast(program_ast_root);
printf("AST memory freed.\n");
// 6. 关闭词法分析器,释放文件资源
close_lexer();
printf("--- C Compiler Frontend Finished ---\n");
return EXIT_SUCCESS;
}
3.2 example.c
:用于测试的C语言源代码示例
这个C文件包含我们前端所支持的各种语法结构,用于验证词法分析和语法分析的正确性。
// 文件名称: example.c
// 用于测试C语言编译器前端的示例源代码。
// 包含变量声明、函数定义、控制流语句和各种表达式。
/*
* 这是一个多行注释的例子。
* 它应该被词法分析器完全忽略。
*/
// 这是一个单行注释。
// 全局变量声明(简化,目前只支持函数内部)
// int global_var = 100;
int sum_up_to(int n) { // 函数定义
int current_sum = 0; // 变量声明并初始化
int i = 0; // 循环变量
while (i <= n) { // while 循环
current_sum = current_sum + i; // 算术运算和赋值
i = i + 1; // 递增
if (i == 5) {
// 测试嵌套的if语句和break/continue
if (current_sum > 10) {
break; // 如果满足条件,退出while循环
} else {
continue; // 否则跳过本次循环剩余部分
}
}
}
return current_sum; // 返回语句
}
int max(int a, int b) {
if (a > b) { // if-else 语句
return a;
} else {
return b;
}
}
int calculate_something(int x, int y, int z) { // 多个参数
int intermediate_result = 0;
// 赋值表达式
intermediate_result = x * y + z / 2; // 混合算术运算,注意优先级
// 逻辑表达式和关系表达式
if (intermediate_result > 100 && x != 0 || y == z) {
int temp = intermediate_result - 50; // 声明局部变量
return temp;
}
// 复杂表达式:一元操作符,函数调用,括号
int final_value = sum_up_to(max(x, y)) + (-z); // 函数调用嵌套, 一元负号
final_value++; // 后缀增量
--final_value; // 前缀递减
// 条件运算符 (三目运算符)
final_value = (final_value > 0) ? final_value : 0;
return final_value;
}
int main() {
int val1 = 10;
int val2 = 20;
char my_char = 'X'; // 字符常量
float pi_val = 3.14159; // 浮点常量
const char* greeting = "Hello, Compiler!\n"; // 字符串常量
// 调用 calculate_something
int result = calculate_something(val1, val2, 5);
// 空语句
;
// 一个简单的循环来打印值(简化,这里不会真正打印,仅用于语法测试)
int counter = 0;
while (counter < 3) {
counter++;
}
return 0;
}
3.3 编译与运行
将所有 .h
和 .c
文件(lexer.h
, lexer.c
, ast.h
, ast.c
, parser.h
, parser.c
, main.c
以及 example.c
)放在同一个目录下。
在Linux/macOS终端中使用 gcc
编译:
gcc -o my_compiler lexer.c ast.c parser.c main.c -std=c99 -Wall -Wextra
然后运行编译器并传入 example.c
作为输入:
./my_compiler example.c
您将看到词法分析和语法分析的过程信息,以及最终生成的抽象语法树的详细层次结构。如果源代码中存在词法或语法错误,程序将报错并指出错误位置。
这将为您提供一个全面的、动手实践的编译前端体验。
4. 总结与展望
在本系列的第二部分,我们深入剖析了C语言编译器的“前端魔法”——词法分析和语法分析。
我们首先回顾了词法分析的核心概念,并通过一个详尽的C语言实现,展示了如何将源代码的字符流转化为有意义的词法单元(Token)流。这包括了高效的输入缓冲区管理、多字符Token的“向前看”识别、各种字面量和操作符的处理,以及对空白字符和注释的过滤。
随后,我们转向了语法分析。我们深入理解了上下文无关文法(CFG)作为语言语法形式化描述的重要性,并基于此设计了能够表示C语言程序结构的抽象语法树(AST)。最重要的是,我们详细实现了递归下降解析器,为文法中的每个非终结符编写了对应的解析函数,并通过递归调用和Token匹配,将Token流逐步构建成一棵完整的AST。这其中,我们还处理了表达式中的运算符优先级和结合性,这是一个常见的解析挑战。
通过本部分的学习和实践,您现在应该能够:
-
清晰地辨识源代码中的“单词”和“句子结构”。
-
理解词法分析器和语法分析器是如何协同工作的。
-
掌握如何用C语言构建一个简化的编译器前端,将源代码转化为AST。
AST是编译器后续阶段进行各种高级分析和转化的基础。它不再是扁平的文本,而是程序逻辑和结构的精确模型。
然而,我们目前的AST还只是一个“骨架”。它知道程序的结构,但还不知道其“意义”:例如,一个变量是否被声明过,它的类型是什么;一个函数调用是否使用了正确的参数类型和数量;不同类型数据之间的操作是否合法。这些“意义”上的检查,正是下一阶段——**语义分析(Semantic Analysis)**的核心任务。
在第三部分,我们将聚焦于编译器的语义分析和中间代码生成。我们将学习如何遍历AST,使用符号表来管理变量和函数的声明信息,执行类型检查以确保程序逻辑的合法性,并将语义正确但仍是高级抽象的AST转化为更接近机器、但仍独立于具体CPU架构的中间表示(Intermediate Representation, IR)。这将是连接前端和后端,实现代码优化的关键一步。
---------------------------------------------------------------------------------------------------------------------------更新于2025.6.1号下午
C 语言编译器自制笔记 (第三部分): 语义分析与中间代码
引言:从语法结构到程序“意义”的飞跃
在系列的第一部分,我们从宏观上理解了 C 语言、汇编语言和编译器的基本概念。在第二部分,我们深入探索了编译器前端的“魔法”,亲手构建了一个能够将 C 源代码分解为词法单元流(Token),并进一步组织成**抽象语法树(Abstract Syntax Tree, AST)**的词法分析器和语法分析器。至此,我们的编译器已经能够识别出程序的“单词”和“句子结构”。
然而,仅仅知道语法结构是不够的。考虑以下 C 语言代码片段:
int a = 10;
char b = 'X';
a = b + 5; // (1)
float func(int x); // 函数声明
func(b); // (2)
undefined_var = 1; // (3)
尽管这段代码在词法和语法上可能都是合法的(即,它由合法的 Token 组成,并且这些 Token 的排列符合 C 语言的文法规则),但它在“意义”上却存在问题:
-
(1)
a = b + 5;
:char
类型的b
与int
类型的5
相加,然后赋值给int
类型的a
。C 语言允许这种混合运算,但需要进行类型转换(隐式或显式)。编译器需要知道如何处理这些类型规则。 -
(2)
func(b);
:func
函数期望一个int
类型的参数,但我们传入了一个char
类型的b
。这是否允许?如果允许,是否需要进行类型转换? -
(3)
undefined_var = 1;
:undefined_var
从未被声明过。这是一个明显的错误,但词法分析器和语法分析器无法发现它,因为它们只关心符号的模式和位置,而不关心其是否已定义。
这些“意义”上的检查和处理,正是编译器的下一个重要阶段——**语义分析(Semantic Analysis)**的职责。
本部分将深入探讨语义分析的核心原理,并引入编译过程中的一个关键中间产物——中间表示(Intermediate Representation, IR),它为后续的代码优化和代码生成提供了独立于机器的抽象。我们将继续通过 C 语言代码,一步步实现这些概念。
我们将涵盖以下内容:
-
1. 语义分析的核心任务与重要性: 深入理解语义分析的目的,它如何弥补词法和语法分析的不足。
-
2. 符号表(Symbol Table)的构建与管理: 详细讲解符号表的数据结构及其在不同作用域下管理标识符信息(类型、种类、地址等)的机制。
-
3. 类型系统与类型检查: 探讨 C 语言的基本类型系统,以及编译器如何根据类型规则进行兼容性检查、隐式类型转换的推断,并报告类型错误。
-
4. 语义错误处理: 讨论常见的语义错误类型及其报告方式。
-
5. 中间表示(Intermediate Representation, IR)的引入: 解释为什么需要 IR,它在编译过程中的作用,并介绍**三地址码(Three-Address Code, TAC)**作为一种常用的 IR 形式。
-
6. 从 AST 到 IR 的转换: 讲解如何遍历语义分析后的 AST,并生成对应的三地址码序列。
通过本部分的学习,您将能够:
-
理解程序“意义”的检查过程,以及编译器如何理解语言的语义规则。
-
掌握符号表和类型检查的实现原理。
-
了解中间表示的重要性及其基本形式。
-
亲手编写并运行一个能够进行初步语义分析并生成中间代码的编译器组件,进一步揭开编译器的内部奥秘。
1. 语义分析的核心任务与重要性
**语义分析(Semantic Analysis)**是编译器的逻辑检查阶段。它发生在词法分析和语法分析之后,但通常在中间代码生成之前。其主要任务是验证源代码的“意义”是否符合语言规范,并收集必要的信息,为后续的代码生成和优化做准备。
如果说词法分析是识别单词,语法分析是识别句子结构,那么语义分析就是理解句子的含义,并检查其是否合理、是否有意义。
1.1 语义分析的目的
语义分析主要致力于解决以下问题:
-
合法性验证: 确保程序在逻辑上是有效的。例如,所有使用的变量都必须先声明,并且类型必须匹配操作。
-
信息收集与修饰 AST: 收集关于标识符、表达式和类型的信息,并将这些信息附加到 AST 节点或符号表中。这些附加信息(例如表达式的最终类型、变量在内存中的偏移量)对后续的代码生成和优化至关重要。语义分析阶段会“装饰” AST,使得 AST 节点包含更丰富的语义信息。例如,一个表达式节点在语义分析后会附带上其计算结果的类型。
-
错误报告: 识别并报告语义错误,如类型不匹配、未声明的变量、函数参数数量或类型错误、重复定义等。
-
隐式类型转换(Implicit Type Coercion): 根据语言规则推断并插入必要的隐式类型转换操作。例如,
int a = 1.5;
会在 AST 中插入一个从float
到int
的类型转换节点。
1.2 语义分析的关键活动
语义分析通常涉及以下主要活动:
-
符号表管理(Symbol Table Management): 构建和维护一个或多个符号表。符号表是一个关键的数据结构,它存储了程序中所有标识符(变量、函数、参数、结构体、枚举等)的详细信息,包括它们的类型、种类、作用域、存储位置等。
-
类型检查(Type Checking): 确保程序中的操作(如算术运算、赋值、函数调用)涉及的操作数类型兼容,并根据需要推断表达式的最终类型。这是语义分析的核心任务之一。
-
流控制检查: 验证控制流语句(如
break
,continue
,return
)的使用是否合法(例如,break
和continue
必须在循环或switch
语句内部;return
语句的返回值类型必须与函数声明的返回类型兼容)。 -
唯一性检查: 确保标识符在同一作用域内不被重复定义。
-
初始化检查: 检查变量在使用前是否被初始化(尽管这不是 C 语言强制的,但好的编译器会给出警告)。
-
L-value 和 R-value 检查: 验证赋值操作符左侧的表达式(L-value)是否可以被赋值(例如,不能给常量赋值)。
语义分析是连接编译器前端(词法、语法)和后端(中间代码、优化、代码生成)的桥梁。它确保了 AST 是语义正确的,为后续生成有效的机器代码提供了坚实的基础。
2. 符号表(Symbol Table)的构建与管理
**符号表(Symbol Table)**是编译器在编译过程中用于存储和检索所有标识符(符号)信息的核心数据结构。它就像一个字典,键是标识符的名称,值是关于该标识符的所有相关属性。
2.1 符号表的作用
-
存储标识符属性: 记录标识符的名称、类型(如
int
,char*
)、种类(变量、函数、参数)、作用域、在内存中的地址或偏移量等。对于函数,还需要存储其参数列表(数量和类型)和返回类型。对于数组,需要存储其维度和大小。 -
作用域管理: 跟踪标识符的可见范围。C 语言有块级作用域(
{}
)、函数作用域、文件作用域和全局作用域。符号表需要支持进入和退出作用域,确保正确查找标识符。当查找一个标识符时,编译器会从当前作用域开始,沿着作用域链向上查找,直到找到第一个匹配项或到达全局作用域。 -
重复定义检查: 在当前作用域内,检查是否存在同名标识符的重复定义。这是语义错误。
-
未定义引用检查: 在查找标识符时,如果当前作用域及所有父作用域中都找不到该标识符,则报告未定义引用错误。这也是语义错误。
-
内存分配辅助: 在中间代码生成或代码生成阶段,符号表中记录的偏移量(offset)信息对于计算变量在栈帧或数据段中的实际内存地址至关重要。
2.2 符号表的数据结构设计
我们将实现一个简化的符号表,它支持嵌套作用域。我们使用链表来管理作用域,每个作用域内部使用一个固定大小数组(为了简化,实际编译器会用哈希表或动态数组,以提高查找效率和灵活性)来存储符号条目。
核心结构体:
-
DataType
枚举: 表示 C 语言的基本数据类型。 -
SymbolKind
枚举: 表示标识符的种类(变量、函数、参数)。 -
SymbolEntry
结构体: 存储单个标识符的详细信息。 -
Scope
结构体: 表示一个作用域,包含符号条目列表和指向父作用域的指针。 -
current_scope
(全局指针): 指向当前活跃的作用域。
2.2.1 symbol_table.h
:符号表接口定义
// 文件名称: symbol_table.h
// 符号表模块的接口定义,包括数据类型、符号种类、符号条目和作用域的结构,
// 以及符号表操作函数。
#ifndef SYMBOL_TABLE_H
#define SYMBOL_TABLE_H
#include <stdio.h> // for FILE*, printf, fprintf
#include <stdlib.h> // for malloc, free, exit
#include <string.h> // for strdup, strcmp
#include <stdbool.h> // for bool
// 引入AST和Lexer的头文件,用于错误报告的位置信息
// 这两个头文件应包含行号和列号的结构或字段定义
#include "ast.h"
#include "lexer.h" // 假设 lexer.h 定义了 TokenPos 或类似结构
// --- 数据类型枚举 ---
// 定义C语言支持的基本数据类型。
typedef enum {
TYPE_UNKNOWN = 0, // 未知类型或错误类型
TYPE_VOID, // void (通常用于函数返回值)
TYPE_INT, // int
TYPE_CHAR, // char
TYPE_FLOAT, // float
TYPE_DOUBLE, // double
// TODO: 扩展:TYPE_ARRAY, TYPE_POINTER, TYPE_STRUCT 等
} DataType;
// --- 符号种类枚举 ---
// 定义标识符在程序中的不同角色。
typedef enum {
SYM_VAR, // 变量 (包括局部变量和全局变量)
SYM_FUNC, // 函数
SYM_PARAM // 函数参数
// TODO: 扩展:SYM_STRUCT_TAG, SYM_UNION_TAG, SYM_ENUM_TAG, SYM_TYPEDEF 等
} SymbolKind;
// --- 函数参数信息结构体 (用于函数类型的 SymbolEntry) ---
typedef struct FunctionParam {
DataType type; // 参数类型
// TODO: 扩展:char *name; // 参数名 (可选,主要用于调试和特定检查)
} FunctionParam;
// --- 符号条目结构体 ---
// 存储每个标识符的详细信息。
typedef struct SymbolEntry {
char *name; // 标识符名称 (例如: "main", "count", "sum_up_to")
DataType type; // 标识符的数据类型 (例如: TYPE_INT, TYPE_VOID)
SymbolKind kind; // 标识符的种类 (例如: SYM_VAR, SYM_FUNC)
int scope_level; // 标识符所在的作用域级别 (0为全局,1为顶层函数,2为内层块等)
int offset; // 在其作用域内相对于基址的偏移量 (用于局部变量和参数的内存分配)
// 对于函数,此字段可能不使用或表示其他信息 (如函数地址)
// --- 针对不同种类符号的特定信息 ---
union {
// For SYM_FUNC
struct {
DataType return_type; // 函数返回类型
FunctionParam *params; // 参数类型列表 (动态数组)
int num_params; // 参数数量
// TODO: bool is_defined; // 函数是否已定义 (非仅声明)
} func_info;
// For SYM_VAR / SYM_PARAM
struct {
// TODO: int array_dimensions[MAX_ARRAY_DIMENSIONS]; // 数组维度
// TODO: int num_dimensions; // 数组维度数量
// TODO: bool is_global; // 是否是全局变量
} var_info;
} details;
ASTNode *decl_node; // 指向声明此符号的AST节点,用于获取位置信息或其他上下文
} SymbolEntry;
// 每个作用域内符号条目的最大数量(简化限制,实际编译器会用哈希表或动态数组)
#define MAX_SYMBOLS_PER_SCOPE 128
// --- 作用域结构体 ---
// 表示一个作用域,包含其内部的符号条目和指向父作用域的指针。
typedef struct Scope {
SymbolEntry *entries[MAX_SYMBOLS_PER_SCOPE]; // 当前作用域内的符号条目数组
int num_entries; // 当前作用域内的符号条目数量
struct Scope *parent; // 指向父作用域的指针 (用于作用域链查找)
int level; // 作用域级别
int current_offset; // 当前作用域内局部变量的下一个可用偏移量(用于栈帧计算)
// TODO: 可以添加一个用于跟踪当前作用域内栈帧总大小的字段,用于代码生成
} Scope;
// --- 符号表全局状态 ---
// 指向当前活跃的作用域。通过修改此指针来进入/退出作用域。
extern Scope *current_scope;
extern int current_scope_level; // 当前作用域级别
// --- 符号表操作函数声明 ---
/**
* @brief 初始化符号表,创建全局作用域。
*/
void init_symbol_table();
/**
* @brief 进入一个新的作用域(例如:函数体或代码块)。
* 创建一个新的Scope结构,并将其父作用域设置为当前的current_scope。
*/
void enter_scope();
/**
* @brief 退出当前作用域。
* 释放当前作用域占用的内存,并将current_scope指针恢复到父作用域。
* @param line 退出作用域的行号,用于错误报告。
* @param col 退出作用域的列号,用于错误报告。
*/
void exit_scope(int line, int col);
/**
* @brief 在当前作用域中插入一个新符号。
* 如果符号名已存在于当前作用域,则报告错误。
* @param name 符号名称。
* @param type 符号数据类型。
* @param kind 符号种类。
* @param decl_node 指向声明此符号的AST节点,用于获取位置信息。
* @return 插入的SymbolEntry指针。
*/
SymbolEntry *insert_symbol(const char *name, DataType type, SymbolKind kind, ASTNode *decl_node);
/**
* @brief 在当前作用域链中查找符号。
* 从当前作用域开始向上查找,直到全局作用域。
* @param name 要查找的符号名称。
* @return 如果找到,返回SymbolEntry指针;否则返回NULL。
*/
SymbolEntry *lookup_symbol(const char *name);
/**
* @brief 在当前作用域中查找符号 (不向上查找)。
* @param name 要查找的符号名称。
* @return 如果在当前作用域找到,返回SymbolEntry指针;否则返回NULL。
*/
SymbolEntry *lookup_symbol_in_current_scope(const char *name);
/**
* @brief 获取数据类型的大小(字节数)。
* 用于计算变量在内存中的偏移量。
* @param type 数据类型。
* @return 字节数。
*/
int get_type_size(DataType type);
/**
* @brief 辅助函数:将 DataType 枚举转换为可读的字符串。
* @param type DataType值。
* @return 对应的字符串。
*/
const char *data_type_to_string(DataType type);
/**
* @brief 辅助函数:将 SymbolKind 枚举转换为可读的字符串。
* @param kind SymbolKind值。
* @return 对应的字符串。
*/
const char *symbol_kind_to_string(SymbolKind kind);
/**
* @brief 打印当前作用域链中所有作用域的符号表内容(用于调试)。
*/
void print_symbol_table();
/**
* @brief 释放符号表占用的所有内存。
*/
void free_symbol_table();
#endif // SYMBOL_TABLE_H
解释与扩展:
-
SymbolEntry
结构体: 增加了union
来存储不同种类符号的特定信息,例如函数会有返回类型、参数列表等。decl_node
指针非常有用,可以快速追溯到符号的声明位置,方便错误报告。 -
Scope
结构体:current_offset
字段对于后续代码生成阶段计算局部变量和参数在栈帧中的相对地址至关重要。每进入一个作用域,这个偏移量通常从 0 开始或从某个固定值开始递增,表示变量相对于栈帧基址的存储位置。 -
init_symbol_table()
: 这个函数会在编译器启动时被调用一次,用于创建最外层的全局作用域(level = 0
)。 -
enter_scope()
: 每次遇到一个新的作用域(例如函数定义体、if
语句后的{}
块、for
/while
循环体),都会调用此函数。它会创建一个新的Scope
对象,将其parent
指针设置为当前的current_scope
,并更新current_scope
为新创建的作用域。这形成了一个作用域链。 -
exit_scope()
: 当编译器解析完一个作用域(例如,遇到}
字符)时,调用此函数。它会释放当前作用域的内存,并将current_scope
指针回溯到其父作用域。 -
insert_symbol()
: 在当前current_scope
中添加一个新的SymbolEntry
。在添加之前,它需要检查当前作用域中是否已经存在同名的标识符,如果是,则报告“重复定义”错误。 -
lookup_symbol()
: 这是最重要的查找函数。它会从current_scope
开始,沿着parent
指针链向上查找,直到找到第一个匹配的标识符或者到达全局作用域的末尾。如果最终没有找到,则报告“未声明标识符”错误。lookup_symbol_in_current_scope
是一个辅助函数,只在当前作用域查找。 -
get_type_size()
: 这个函数对于计算变量的内存占用和后续的内存偏移量至关重要。
简化的 symbol_table.c
核心逻辑(概念性) 为了演示其工作原理,我们可以概念性地勾勒出 symbol_table.c
的核心实现逻辑。
// 文件名称: symbol_table.c
// 符号表模块的实现
#include "symbol_table.h"
#include "compiler_errors.h" // 假设有一个错误报告模块
Scope *current_scope = NULL;
int current_scope_level = -1; // -1表示未初始化,0为全局
// 辅助函数:创建一个新的SymbolEntry
static SymbolEntry *create_symbol_entry(const char *name, DataType type, SymbolKind kind, ASTNode *decl_node) {
SymbolEntry *entry = (SymbolEntry *)malloc(sizeof(SymbolEntry));
if (!entry) {
// 内存分配失败,编译器错误
fprintf(stderr, "Fatal error: Out of memory for SymbolEntry!\n");
exit(EXIT_FAILURE);
}
entry->name = strdup(name); // 复制字符串
if (!entry->name) {
fprintf(stderr, "Fatal error: Out of memory for symbol name!\n");
free(entry);
exit(EXIT_FAILURE);
}
entry->type = type;
entry->kind = kind;
entry->scope_level = current_scope_level;
entry->offset = current_scope ? current_scope->current_offset : 0; // 记录偏移量
entry->decl_node = decl_node;
// 根据kind初始化union
if (kind == SYM_FUNC) {
entry->details.func_info.return_type = TYPE_UNKNOWN; // 待填充
entry->details.func_info.params = NULL;
entry->details.func_info.num_params = 0;
}
// 更新当前作用域的下一个可用偏移量 (简化: 不考虑对齐,只按类型大小递增)
if (current_scope && (kind == SYM_VAR || kind == SYM_PARAM)) {
current_scope->current_offset += get_type_size(type);
}
return entry;
}
// 辅助函数:释放SymbolEntry
static void free_symbol_entry(SymbolEntry *entry) {
if (entry) {
free(entry->name);
if (entry->kind == SYM_FUNC && entry->details.func_info.params) {
free(entry->details.func_info.params); // 释放参数列表
}
free(entry);
}
}
void init_symbol_table() {
current_scope = NULL; // 确保是空,防止重复初始化
current_scope_level = -1;
enter_scope(); // 创建全局作用域
// 可以预定义一些内置函数或类型
// insert_symbol("printf", TYPE_INT, SYM_FUNC, NULL); // 示例
// ...
}
void enter_scope() {
Scope *new_scope = (Scope *)malloc(sizeof(Scope));
if (!new_scope) {
fprintf(stderr, "Fatal error: Out of memory for Scope!\n");
exit(EXIT_FAILURE);
}
new_scope->num_entries = 0;
new_scope->parent = current_scope;
new_scope->level = current_scope_level + 1;
new_scope->current_offset = (current_scope_level == -1) ? 0 : current_scope->current_offset; // 全局或上一层作用域的偏移量继承
current_scope = new_scope;
current_scope_level++;
// fprintf(stderr, "DEBUG: Entered scope level %d\n", current_scope_level);
}
void exit_scope(int line, int col) {
if (current_scope == NULL) {
// 理论上不应该发生,除非作用域管理逻辑有误
compiler_error(line, col, "Attempting to exit null scope.");
return;
}
// 释放当前作用域内的所有符号条目
for (int i = 0; i < current_scope->num_entries; i++) {
free_symbol_entry(current_scope->entries[i]);
}
Scope *parent_scope = current_scope->parent;
free(current_scope); // 释放当前作用域结构体
current_scope = parent_scope;
current_scope_level--;
// fprintf(stderr, "DEBUG: Exited scope level %d\n", current_scope_level);
}
SymbolEntry *insert_symbol(const char *name, DataType type, SymbolKind kind, ASTNode *decl_node) {
// 检查当前作用域是否已存在同名符号
SymbolEntry *existing_symbol = lookup_symbol_in_current_scope(name);
if (existing_symbol) {
compiler_error(decl_node->token_pos.line, decl_node->token_pos.col,
"Redefinition of '%s' in the same scope.", name);
compiler_error(existing_symbol->decl_node->token_pos.line, existing_symbol->decl_node->token_pos.col,
"Previous definition was here.");
return existing_symbol; // 返回现有符号,但报告错误
}
if (current_scope->num_entries >= MAX_SYMBOLS_PER_SCOPE) {
compiler_error(decl_node->token_pos.line, decl_node->token_pos.col,
"Too many symbols in current scope. Max %d allowed.", MAX_SYMBOLS_PER_SCOPE);
return NULL;
}
SymbolEntry *new_entry = create_symbol_entry(name, type, kind, decl_node);
current_scope->entries[current_scope->num_entries++] = new_entry;
return new_entry;
}
SymbolEntry *lookup_symbol(const char *name) {
Scope *scope_ptr = current_scope;
while (scope_ptr != NULL) {
for (int i = 0; i < scope_ptr->num_entries; i++) {
if (strcmp(scope_ptr->entries[i]->name, name) == 0) {
return scope_ptr->entries[i];
}
}
scope_ptr = scope_ptr->parent; // 向上查找
}
return NULL; // 未找到
}
SymbolEntry *lookup_symbol_in_current_scope(const char *name) {
if (current_scope == NULL) return NULL; // should not happen after init
for (int i = 0; i < current_scope->num_entries; i++) {
if (strcmp(current_scope->entries[i]->name, name) == 0) {
return current_scope->entries[i];
}
}
return NULL;
}
int get_type_size(DataType type) {
switch (type) {
case TYPE_INT: return 4; // 假设 int 是 4 字节
case TYPE_CHAR: return 1; // 假设 char 是 1 字节
case TYPE_FLOAT: return 4; // 假设 float 是 4 字节
case TYPE_DOUBLE: return 8; // 假设 double 是 8 字节
case TYPE_VOID: return 0;
case TYPE_UNKNOWN:
default:
// 应该报告错误或警告,未知类型无法确定大小
fprintf(stderr, "Warning: Attempted to get size of unknown/void type.\n");
return 0;
}
}
const char *data_type_to_string(DataType type) {
switch (type) {
case TYPE_UNKNOWN: return "UNKNOWN";
case TYPE_VOID: return "VOID";
case TYPE_INT: return "INT";
case TYPE_CHAR: return "CHAR";
case TYPE_FLOAT: return "FLOAT";
case TYPE_DOUBLE: return "DOUBLE";
default: return "INVALID_TYPE";
}
}
const char *symbol_kind_to_string(SymbolKind kind) {
switch (kind) {
case SYM_VAR: return "VAR";
case SYM_FUNC: return "FUNC";
case SYM_PARAM: return "PARAM";
default: return "INVALID_KIND";
}
}
void print_symbol_table() {
fprintf(stderr, "\n--- Symbol Table Contents (Current Scope Chain) ---\n");
Scope *scope_ptr = current_scope;
while (scope_ptr != NULL) {
fprintf(stderr, "Scope Level %d (Offset: %d):\n", scope_ptr->level, scope_ptr->current_offset);
if (scope_ptr->num_entries == 0) {
fprintf(stderr, " (No entries)\n");
} else {
for (int i = 0; i < scope_ptr->num_entries; i++) {
SymbolEntry *entry = scope_ptr->entries[i];
fprintf(stderr, " - Name: %s, Type: %s, Kind: %s, Offset: %d (Decl: Line %d, Col %d)\n",
entry->name, data_type_to_string(entry->type), symbol_kind_to_string(entry->kind),
entry->offset, entry->decl_node->token_pos.line, entry->decl_node->token_pos.col);
}
}
scope_ptr = scope_ptr->parent;
}
fprintf(stderr, "-----------------------------------------------\n");
}
void free_symbol_table_recursive(Scope *scope) {
if (scope == NULL) return;
// Recursively free parent scopes first if not already handled
// This is more complex if we're not just iterating current_scope to NULL.
// Simpler: rely on exit_scope freeing current scope.
// For a full cleanup from global scope, iterate through children or keep a list of all scopes.
// For now, exit_scope handles freeing as it pops.
// If we want to free everything from arbitrary point, we need to iterate all entries then free scope struct.
// This function is generally for final cleanup, assuming exit_scope didn't free its entries.
// For this simplified example, exit_scope already frees its entries.
// So this recursive cleanup is for a scenario where exit_scope *doesn't* free entries.
// For now, let's just make sure we free the root scope and its entries if called.
if (scope->num_entries > 0) {
for(int i = 0; i < scope->num_entries; ++i) {
free_symbol_entry(scope->entries[i]);
}
}
free(scope);
}
void free_symbol_table() {
// Traverse the scope chain and free all scopes and their entries
while (current_scope != NULL) {
Scope *temp_scope = current_scope;
current_scope = current_scope->parent; // Move to parent
// Free entries in temp_scope
for (int i = 0; i < temp_scope->num_entries; i++) {
free_symbol_entry(temp_scope->entries[i]);
}
free(temp_scope); // Free the scope struct itself
}
current_scope_level = -1;
// fprintf(stderr, "DEBUG: All symbol table memory freed.\n");
}
3. 类型系统与类型检查
**类型系统(Type System)**是编程语言中关于数据类型及其操作规则的集合。它定义了程序中值的类别(如整数、浮点数、字符、指针、结构体等),以及这些类别之间允许或禁止的操作。**类型检查(Type Checking)**是语义分析阶段的核心任务,旨在验证程序中所有操作的数据类型是否合法和兼容。
3.1 C 语言的基本类型系统
C 语言的类型系统是静态的(在编译时确定类型)且相对简单但功能强大:
-
基本类型(Primitive Types):
-
整数类型:
char
,short
,int
,long
,long long
(各有signed
和unsigned
版本)。 -
浮点类型:
float
,double
,long double
。 -
空类型:
void
(用于表示无返回值函数或通用指针)。
-
-
派生类型(Derived Types):
-
数组(Arrays): 同类型元素的集合。
-
指针(Pointers): 存储内存地址的变量。
-
结构体(Structs): 不同类型成员的集合。
-
联合体(Unions): 在同一内存位置存储不同类型成员,但一次只能使用一个。
-
枚举(Enums): 用户定义的整数常量集合。
-
函数类型: 函数的返回类型和参数列表。
-
-
类型定义(
typedef
): 允许为现有类型创建新的别名。
3.2 类型兼容性规则与类型转换
C 语言对类型兼容性和转换有一套严格而复杂的规则:
-
赋值兼容性: 赋值操作符
=
的左操作数(L-value)和右操作数(R-value)的类型必须兼容。如果类型不完全匹配,编译器会尝试进行隐式类型转换(Implicit Type Conversion 或 Type Coercion)。 -
算术/逻辑运算兼容性: 对于二元运算符(
+
,-
,*
,/
,&&
,||
等),操作数的类型必须兼容。如果类型不同,C 语言会执行“常规算术转换”(Usual Arithmetic Conversions)来将操作数提升到共同的类型,以防止精度损失。-
整数提升(Integer Promotion):
char
和short int
(以及枚举类型)在表达式中会被提升为int
或unsigned int
。 -
类型提升顺序: 浮点类型优先于整数类型。
long double
>double
>float
;unsigned long long
>long long
>unsigned long
>long
>unsigned int
>int
。 例如,int + float
会将int
提升为float
,结果为float
。
-
-
函数调用兼容性: 函数实参的类型必须与形参的类型兼容。如果类型不匹配,编译器会尝试隐式转换。如果无法转换或参数数量不匹配,则报告错误。
-
显式类型转换(Explicit Type Conversion / Casting): 程序员可以使用
(type)
运算符强制进行类型转换。例如,(float)a_int
。
3.3 类型检查的过程
类型检查器通常在遍历 AST 的过程中完成类型推断和验证。它会为 AST 的每个表达式节点附加一个 DataType
属性,表示该表达式计算结果的类型。
-
自底向上推断(Bottom-Up Inference):
-
从 AST 的叶子节点(如变量引用、常量)开始。变量的类型从符号表中查找。常量的类型根据其字面量确定(如
10
是int
,3.14
是double
)。 -
对于二元表达式节点(如
a + b
):-
递归地获取左操作数
a
的类型T_a
和右操作数b
的类型T_b
。 -
根据操作符(
+
)和类型提升规则,确定最终的表达式结果类型T_result
。 -
如果
T_a
和T_b
不兼容或无法提升,则报告“类型不匹配”错误。 -
如果发生了隐式类型转换,可以在 AST 中插入一个“类型转换”节点,或者在生成 IR 时体现。
-
-
对于一元表达式节点(如
-x
):-
递归地获取操作数
x
的类型T_x
。 -
根据操作符(
-
)和T_x
确定结果类型。 -
如果
T_x
不兼容,则报告错误。
-
-
-
L-value 和 R-value 检查:
-
L-value(左值):表示一个存储区域,可以出现在赋值操作符的左侧。例如,变量名。
-
R-value(右值):表示一个值,可以出现在赋值操作符的右侧。
-
类型检查器会验证赋值操作符
=
的左侧是否是一个有效的 L-value。
-
-
函数调用检查:
-
当遇到函数调用节点时:
-
从符号表中查找函数名,获取其声明信息(返回类型、参数数量、参数类型)。
-
递归地对每个实参表达式进行类型检查,获取其实参类型。
-
比较实参的数量和类型与形参声明是否匹配。如果数量不匹配,报告“参数数量不匹配”错误。如果类型不兼容且无法隐式转换,报告“参数类型不匹配”错误。
-
函数调用的结果类型就是函数的返回类型。
-
-
-
控制流语句检查:
-
return
语句:检查return
后表达式的类型是否与当前函数声明的返回类型兼容。 -
break
和continue
语句:检查它们是否出现在循环(for
,while
,do-while
)或switch
语句的内部。
-
代码示例:AST 节点类型装饰与简单类型检查
假设我们的 ASTNode
结构体包含一个 DataType node_type;
字段,用于存储该节点代表的表达式的推断类型。
// 在 ast.h 中 (假设)
typedef enum { /* ...各种节点类型... */ } ASTNodeType;
typedef struct ASTNode {
ASTNodeType type;
TokenPos token_pos; // 词元位置信息,用于错误报告
// ... 更多子节点和数据 ...
DataType inferred_type; // 语义分析后,存储该表达式节点的推断类型
} ASTNode;
// 在 semantic_analyzer.h (假设)
// ...
// 简单的类型推断和检查函数原型
DataType infer_expr_type(ASTNode *expr_node);
void check_stmt_semantics(ASTNode *stmt_node);
// 在 semantic_analyzer.c (假设)
#include "semantic_analyzer.h"
#include "symbol_table.h"
#include "compiler_errors.h" // 错误报告模块
// 辅助函数:推断字面量的类型
static DataType get_literal_type(ASTNode *literal_node) {
switch (literal_node->type) {
case AST_INT_LITERAL: return TYPE_INT;
case AST_FLOAT_LITERAL: return TYPE_FLOAT;
case AST_CHAR_LITERAL: return TYPE_CHAR;
// TODO: AST_STRING_LITERAL
default: return TYPE_UNKNOWN;
}
}
// 辅助函数:根据二元操作符和操作数类型推断结果类型和检查兼容性
static DataType infer_binary_op_type(ASTNodeType op_type, DataType left_type, DataType right_type, int line, int col) {
if (left_type == TYPE_UNKNOWN || right_type == TYPE_UNKNOWN) {
return TYPE_UNKNOWN; // 如果操作数类型未知,则结果也未知
}
// 简单规则:int 和 float 之间,提升为 float
if ((left_type == TYPE_INT && right_type == TYPE_FLOAT) ||
(left_type == TYPE_FLOAT && right_type == TYPE_INT)) {
return TYPE_FLOAT;
}
// 相同类型,结果就是该类型
if (left_type == right_type) {
// 对于除法,如果操作数是整数,结果通常也是整数(C语言整数除法)
if ((op_type == AST_DIV) && (left_type == TYPE_INT)) {
// 整数除法,结果仍是int
}
return left_type;
}
// 如果是逻辑操作 (&&, ||),结果是 int (布尔值)
if (op_type == AST_AND || op_type == AST_OR) {
// C语言中任何非零值都为真
if ((left_type == TYPE_INT || left_type == TYPE_CHAR || left_type == TYPE_FLOAT) &&
(right_type == TYPE_INT || right_type == TYPE_CHAR || right_type == TYPE_FLOAT)) {
return TYPE_INT; // 逻辑运算结果是 int
}
}
// 其他类型组合或不兼容操作符
compiler_error(line, col, "Type mismatch for binary operator %s: %s vs %s",
ast_node_type_to_string(op_type), data_type_to_string(left_type), data_type_to_string(right_type));
return TYPE_UNKNOWN;
}
// 主要的类型推断函数
DataType infer_expr_type(ASTNode *expr_node) {
if (!expr_node) return TYPE_UNKNOWN;
DataType result_type = TYPE_UNKNOWN;
switch (expr_node->type) {
case AST_IDENTIFIER_EXPR: {
SymbolEntry *sym = lookup_symbol(expr_node->val.identifier);
if (!sym) {
compiler_error(expr_node->token_pos.line, expr_node->token_pos.col, "Undeclared identifier '%s'.", expr_node->val.identifier);
return TYPE_UNKNOWN;
}
result_type = sym->type;
break;
}
case AST_INT_LITERAL:
case AST_FLOAT_LITERAL:
case AST_CHAR_LITERAL: {
result_type = get_literal_type(expr_node);
break;
}
case AST_BINARY_OP: {
DataType left_type = infer_expr_type(expr_node->left);
DataType right_type = infer_expr_type(expr_node->right);
result_type = infer_binary_op_type(expr_node->val.op_type, left_type, right_type,
expr_node->token_pos.line, expr_node->token_pos.col);
break;
}
case AST_UNARY_OP: {
DataType operand_type = infer_expr_type(expr_node->left);
// 简化:目前只支持 - 和 ! 运算符,对 int/float
if (operand_type == TYPE_INT || operand_type == TYPE_FLOAT) {
result_type = operand_type;
} else {
compiler_error(expr_node->token_pos.line, expr_node->token_pos.col, "Invalid operand type %s for unary operator %s.",
data_type_to_string(operand_type), ast_node_type_to_string(expr_node->val.op_type));
result_type = TYPE_UNKNOWN;
}
break;
}
case AST_ASSIGN_EXPR: { // 赋值表达式,需要检查 L-value 和类型兼容性
// 左侧必须是可赋值的标识符
if (expr_node->left->type != AST_IDENTIFIER_EXPR) {
compiler_error(expr_node->left->token_pos.line, expr_node->left->token_pos.col, "Left-hand side of assignment must be an assignable variable.");
result_type = TYPE_UNKNOWN;
} else {
SymbolEntry *lhs_sym = lookup_symbol(expr_node->left->val.identifier);
if (!lhs_sym) { // 应该在 AST_IDENTIFIER_EXPR 处理时已经报错
result_type = TYPE_UNKNOWN;
} else {
DataType lhs_type = lhs_sym->type;
DataType rhs_type = infer_expr_type(expr_node->right);
if (lhs_type == TYPE_UNKNOWN || rhs_type == TYPE_UNKNOWN) {
result_type = TYPE_UNKNOWN;
} else if (lhs_type == rhs_type) {
result_type = lhs_type; // 赋值表达式的结果类型是左侧变量的类型
} else if ((lhs_type == TYPE_INT && rhs_type == TYPE_FLOAT) ||
(lhs_type == TYPE_FLOAT && rhs_type == TYPE_INT) ||
(lhs_type == TYPE_INT && rhs_type == TYPE_CHAR) ||
(lhs_type == TYPE_CHAR && rhs_type == TYPE_INT)) {
// 允许隐式转换,结果类型是左侧类型
result_type = lhs_type;
// TODO: 在AST中插入类型转换节点
// fprintf(stderr, "DEBUG: Implicit conversion from %s to %s at line %d\n",
// data_type_to_string(rhs_type), data_type_to_string(lhs_type), expr_node->token_pos.line);
} else {
compiler_error(expr_node->token_pos.line, expr_node->token_pos.col, "Type mismatch in assignment: cannot assign %s to %s.",
data_type_to_string(rhs_type), data_type_to_string(lhs_type));
result_type = TYPE_UNKNOWN;
}
}
}
break;
}
case AST_FUNC_CALL_EXPR: {
SymbolEntry *func_sym = lookup_symbol(expr_node->val.func_call.func_name);
if (!func_sym || func_sym->kind != SYM_FUNC) {
compiler_error(expr_node->token_pos.line, expr_node->token_pos.col, "Call to undeclared function or non-function '%s'.", expr_node->val.func_call.func_name);
return TYPE_UNKNOWN;
}
// 检查参数数量
if (expr_node->val.func_call.num_args != func_sym->details.func_info.num_params) {
compiler_error(expr_node->token_pos.line, expr_node->token_pos.col,
"Function '%s' called with %d arguments, but expects %d.",
func_sym->name, expr_node->val.func_call.num_args, func_sym->details.func_info.num_params);
result_type = func_sym->details.func_info.return_type; // 即使有错也尝试返回类型
} else {
// 检查参数类型
for (int i = 0; i < expr_node->val.func_call.num_args; i++) {
ASTNode *arg_expr = expr_node->val.func_call.args[i];
DataType actual_arg_type = infer_expr_type(arg_expr);
DataType expected_param_type = func_sym->details.func_info.params[i].type;
// 简化类型兼容性检查
if (actual_arg_type == TYPE_UNKNOWN) {
result_type = func_sym->details.func_info.return_type; // 无法检查,但继续
continue;
}
if (actual_arg_type != expected_param_type &&
!((actual_arg_type == TYPE_CHAR && expected_param_type == TYPE_INT) ||
(actual_arg_type == TYPE_INT && expected_param_type == TYPE_FLOAT))) { // 示例允许 char->int, int->float
compiler_error(arg_expr->token_pos.line, arg_expr->token_pos.col,
"Type mismatch for argument %d in call to '%s': expected %s, got %s.",
i + 1, func_sym->name, data_type_to_string(expected_param_type), data_type_to_string(actual_arg_type));
}
// TODO: 如果需要,插入隐式转换节点
}
result_type = func_sym->details.func_info.return_type;
}
break;
}
// TODO: 其他表达式类型,如三元运算符、数组访问等
default:
// compiler_error(expr_node->token_pos.line, expr_node->token_pos.col, "Unhandled expression type in type inference.");
result_type = TYPE_UNKNOWN; // 未知或未处理的表达式类型
break;
}
expr_node->inferred_type = result_type; // 装饰 AST 节点
return result_type;
}
4. 语义错误处理
语义错误是程序在逻辑上不符合语言规范的错误。它们在语义分析阶段被发现,通常比词法或语法错误更复杂,因为它们涉及到对程序“意义”的理解。有效的错误处理和报告是编译器用户体验的关键组成部分。
4.1 常见的语义错误类型
-
未声明标识符(Undeclared Identifier): 使用了一个在当前作用域或其父作用域中从未被声明过的变量、函数或类型。
-
示例:
undefined_var = 1;
-
-
重复定义(Redefinition): 在同一作用域内声明了同名的标识符。
-
示例:
int x; int x;
-
-
类型不匹配(Type Mismatch):
-
赋值类型不兼容: 将一个无法隐式转换为左值的类型赋值给左值。
-
示例:
int a = "hello";
-
-
运算类型不兼容: 算术、逻辑或位操作符的操作数类型不兼容。
-
示例:
10 + "string";
-
-
函数调用参数类型/数量不匹配: 调用函数时提供的实参数量或类型与函数声明不符。
-
示例:
func(10, 20);
(如果 func 只有一个参数) -
示例:
func("hello");
(如果 func 期望 int 参数)
-
-
返回值类型不兼容:
return
语句中的表达式类型与函数声明的返回类型不兼容。
-
-
不可赋值的左值(Non-assignable L-value): 尝试给一个不能被修改的表达式赋值,如常量。
-
示例:
10 = x;
-
-
控制流语句使用不当:
break
或continue
出现在非循环或switch
语句中。 -
未初始化的变量使用(Warning): 虽然 C 语言允许,但使用未初始化的局部变量会导致不确定的行为。好的编译器会发出警告。
-
函数声明与定义不一致: 函数的声明(原型)与定义之间的返回类型、参数数量或类型不一致。
4.2 错误报告与恢复
-
清晰的错误消息: 错误消息应该准确指出错误类型、涉及的标识符/操作符,并提供足够的上下文,例如错误发生的行号和列号。如果可能,提供额外信息,如“上一次定义在这里”。
-
错误恢复: 编译器在检测到错误后,不应立即停止。它应该尝试进行错误恢复,以便继续扫描程序的其余部分,发现更多的错误。常见的恢复策略包括:
-
跳过语句: 遇到错误时,跳过当前语句的剩余部分,直到下一个分号或
{}
块的结束。 -
默认类型: 对于未声明的变量,可以暂时为其分配一个默认类型(如
int
),以便继续后续分析。 -
假设正确: 对于某些错误,编译器可以假设用户意图是正确的,然后继续编译,这在实践中需要非常谨慎。
-
代码示例:简化的错误报告模块(compiler_errors.h
/ compiler_errors.c
)
// 文件名称: compiler_errors.h
#ifndef COMPILER_ERRORS_H
#define COMPILER_ERRORS_H
#include <stdio.h>
#include <stdarg.h> // For va_list, va_start, va_end
extern int error_count; // 全局错误计数器
/**
* @brief 报告一个编译器错误。
* @param line 错误发生的行号。
* @param col 错误发生的列号。
* @param format 错误消息的格式字符串(如printf)。
* @param ... 格式字符串的参数。
*/
void compiler_error(int line, int col, const char *format, ...);
/**
* @brief 报告一个编译器警告。
* @param line 警告发生的行号。
* @param col 警告发生的列号。
* @param format 警告消息的格式字符串。
* @param ... 格式字符串的参数。
*/
void compiler_warning(int line, int col, const char *format, ...);
#endif // COMPILER_ERRORS_H
// 文件名称: compiler_errors.c (假设实现)
// #include "compiler_errors.h"
// int error_count = 0; // 全局错误计数器
// void compiler_error(int line, int col, const char *format, ...) {
// error_count++;
// fprintf(stderr, "ERROR: (Line %d, Col %d): ", line, col);
// va_list args;
// va_start(args, format);
// vfprintf(stderr, format, args);
// va_end(args);
// fprintf(stderr, "\n");
// // 可以在这里根据错误数量决定是否退出编译
// // if (error_count > MAX_ERRORS) { exit(1); }
// }
// void compiler_warning(int line, int col, const char *format, ...) {
// fprintf(stderr, "WARNING: (Line %d, Col %d): ", line, col);
// va_list args;
// va_start(args, format);
// vfprintf(stderr, format, args);
// va_end(args);
// fprintf(stderr, "\n");
// }
5. 中间表示(Intermediate Representation, IR)的引入
在完成了语义分析并修饰了 AST 之后,我们就拥有了一个语义正确的程序表示。然而,这个 AST 仍然是相对高层次的,与目标机器的指令集有较大差距。直接从 AST 生成最终的机器代码会非常复杂,尤其是进行代码优化。
这就是**中间表示(Intermediate Representation, IR)**的用武之地。
5.1 为什么需要 IR?
IR 在编译过程中扮演着至关重要的角色:
-
前端与后端的桥梁: IR 作为编译器前端(词法、语法、语义分析)和后端(代码优化、代码生成)之间的接口。前端将源代码转换为 IR,后端从 IR 生成目标代码。
-
机器独立性: IR 是一种独立于任何特定机器架构的抽象表示。这意味着我们可以在 IR 层面执行大量的通用优化,而无需针对每个目标 CPU 架构重新实现。这大大提高了编译器的可移植性和可扩展性。
-
代码优化的最佳舞台: AST 对于高级抽象(如循环、条件)很有用,但对于低级优化(如常量传播、公共子表达式消除)则不够明确。汇编代码则过于具体,优化受限于特定的指令集。IR 处于一个“甜点”位置,其抽象层次适中,既能表达程序的完整语义,又足够接近机器指令,便于进行各种复杂的分析和转换。
-
简化代码生成: 从结构化的 IR 生成目标机器代码比直接从 AST 生成要容易得多,因为 IR 已经将高级语言结构分解为更简单的操作。
5.2 好的 IR 的特性
一个好的 IR 应该具备以下特性:
-
表达能力(Expressiveness): 能够准确无歧义地表示源语言程序的语义。
-
易于生成(Ease of Generation): 前端容易从 AST 转换为 IR。
-
易于分析和转换(Ease of Analysis & Transformation): 便于编译器后端进行数据流分析、控制流分析以及各种优化转换。
-
机器独立性(Machine Independence): 允许进行通用的、与目标机器无关的优化。
5.3 三地址码(Three-Address Code, TAC)作为一种常用 IR
**三地址码(Three-Address Code, TAC)**是一种非常流行且广泛使用的 IR 形式。它的名字来源于每条指令通常最多包含三个地址(变量或常量)。
-
基本形式:
result = operand1 operator operand2
或result = operator operand1
(一元操作) 或result = operand1
(赋值) -
TAC 的主要特性:
-
扁平化(Flat): 不像 AST 那样有嵌套结构,TAC 是一系列线性的指令序列。
-
显式临时变量(Explicit Temporaries): 复合表达式会被分解为一系列简单的操作,每个中间结果都存储在一个显式创建的临时变量中(例如
t1
,t2
)。这使得数据流路径非常清晰。 -
明确的语义: 每条 TAC 指令都非常简单和明确,只执行一个基本操作。
-
-
TAC 的优点:
-
便于数据流分析: 每个操作数的来源和去向都非常清晰,易于分析变量的活跃性、数据定义与使用链等。
-
便于实现各种优化: 例如常量折叠、死代码消除、公共子表达式消除、循环不变代码外提等,都可以在 TAC 层面上高效实现。
-
便于代码生成: TAC 指令与许多汇编指令形式非常接近,使得从 TAC 到汇编代码的翻译变得相对简单。
-
-
C 代码到 TAC 的转换示例:
// C 语言代码 int x = (a + b) * c - d;
// 对应的三地址码 (TAC) t1 = a + b // t1 是临时变量 t2 = t1 * c t3 = t2 - d x = t3 // 最终赋值给变量 x
在这个例子中,复杂的表达式被分解为四个简单的 TAC 指令,每个中间结果都显式地存储在临时变量 `t1`, `t2`, `t3` 中。
5.4 intermediate_code.h
:中间代码接口定义
我们将在 intermediate_code.h
中定义 TAC 指令的结构和相关操作。这个定义已经在前面给出,这里再次强调关键部分:
// 文件名称: intermediate_code.h
// 中间代码 (三地址码 - TAC) 生成模块的接口定义,包括指令、操作数结构和生成函数。
// 在本部分,我们增加了中间代码优化的相关声明。
#ifndef INTERMEDIATE_CODE_H
#define INTERMEDIATE_CODE_H
#include "ast.h" // 引入AST节点定义
#include "symbol_table.h"// 引入DataType
#include <stdio.h> // for printf, fprintf
#include <stdlib.h> // for malloc, free, exit
#include <string.h> // for strdup, sprintf
#include <stdbool.h> // for bool
// --- 操作数类型枚举 ---
typedef enum {
OP_NONE = 0, // 无操作数 (例如:某些跳转指令)
OP_TEMP, // 临时变量 (t1, t2, ...)
OP_LITERAL_INT, // 整数常量 (10, 200)
OP_LITERAL_FLOAT, // 浮点数常量 (3.14)
OP_LITERAL_CHAR, // 字符常量 ('a')
OP_STRING_LABEL, // 字符串字面量 (存储在数据段中,通过标签引用)
OP_IDENTIFIER, // 标识符 (变量名, 函数名)
OP_LABEL // 代码标签 (L1, L2, ...)
} OperandType;
// --- 操作数具体值联合体 ---
typedef union OperandValue {
long int_val; // OP_LITERAL_INT
double float_val; // OP_LITERAL_FLOAT
char char_val; // OP_LITERAL_CHAR
char *str_val; // OP_STRING_LABEL, OP_IDENTIFIER, OP_LABEL (指向名称或标签字符串)
} OperandValue;
// --- 操作数结构体 ---
typedef struct Operand {
OperandType type; // 操作数类型 (例如: OP_TEMP, OP_LITERAL_INT)
OperandValue value; // 操作数具体值
DataType data_type; // 操作数的数据类型 (例如: TYPE_INT, TYPE_FLOAT)
bool is_freed; // 标记此操作数是否被释放过 (用于内存管理和优化阶段的判断)
} Operand;
// --- 指令类型枚举 (InstructionType) ---
typedef enum {
// 算术和一元操作
IR_ADD, IR_SUB, IR_MUL, IR_DIV, IR_MOD,
IR_NEG, // 一元负号
IR_NOT, // 逻辑非
IR_BIT_NOT, // 位非
IR_INC, IR_DEC, // 增量/减量
// 赋值
IR_ASSIGN, // result = op1
// 关系操作
IR_EQ, IR_NE, IR_LT, IR_LE, IR_GT, IR_GE,
// 逻辑操作
IR_AND, IR_OR,
// 位操作
IR_BIT_AND, IR_BIT_OR, IR_BIT_XOR, IR_LSHIFT, IR_RSHIFT,
// 控制流
IR_LABEL, // 标签定义
IR_JMP, // 无条件跳转
IR_JEQ, IR_JNE, IR_JLT, IR_JLE, IR_JGT, IR_JGE, // 条件跳转
// 函数调用
IR_PARAM, // 参数传递
IR_CALL, // 函数调用 (CALL func_name, num_args, result_temp)
IR_RET, // 函数返回
// 内存访问 (简化,直接用变量名/临时变量名作为操作数)
IR_LOAD_VAR, // result = var_name
IR_STORE_VAR, // var_name = op1
// 控制流 (循环内的break/continue)
IR_BREAK,
IR_CONTINUE,
} InstructionType;
// --- 指令结构体 (三地址码形式) ---
typedef struct Instruction {
InstructionType type; // 指令类型
Operand result; // 结果操作数 (例如: temp, variable)
Operand op1; // 第一个操作数
Operand op2; // 第二个操作数 (并非所有指令都有)
int line; // 指令对应的C代码行号 (用于调试)
int col; // 指令对应的C代码列号 (用于调试)
struct Instruction *next; // 指向下一条指令 (链表形式存储IR)
bool is_deleted; // 标记指令是否被删除,用于在IR链表遍历时跳过已删除指令
} Instruction;
// --- 中间代码列表全局状态 ---
extern Instruction *ir_head; // 中间代码链表的头
extern Instruction *ir_tail; // 中间代码链表的尾
extern int temp_counter; // 临时变量计数器 (t1, t2, ...)
extern int label_counter; // 标签计数器 (L1, L2, ...)
// --- 中间代码生成器函数声明 ---
/**
* @brief 初始化中间代码生成器。
* 重置临时变量和标签计数器。
*/
void init_intermediate_code_generator();
/**
* @brief 生成一个新的临时变量操作数。
* @param type 临时变量的数据类型。
* @return 新创建的临时变量操作数。
*/
Operand new_temp(DataType type);
/**
* @brief 生成一个新的标签操作数。
* @param prefix 标签前缀 (例如 "L", "LOOP_START")。
* @return 新创建的标签操作数。
*/
Operand new_label(const char *prefix);
/**
* @brief 创建一个整数常量操作数。
* @param val 整数值。
* @return 创建的Operand。
*/
Operand create_literal_operand_int(long val);
/**
* @brief 创建一个浮点数常量操作数。
* @param val 浮点数值。
* @return 创建的Operand。
*/
Operand create_literal_operand_float(double val);
/**
* @brief 创建一个字符常量操作数。
* @param val 字符值。
* @return 创建的Operand。
*/
Operand create_literal_operand_char(char val);
/**
* @brief 创建一个字符串字面量操作数(通常通过标签引用)。
* @param str 字符串内容。
* @return 创建的Operand。
*/
Operand create_string_label_operand(const char *str);
/**
* @brief 创建一个标识符操作数 (变量或函数)。
* @param name 标识符名称。
* @param type 标识符数据类型。
* @return 创建的Operand。
*/
Operand create_identifier_operand(const char *name, DataType type);
/**
* @brief 创建一个标签操作数。
* @param name 标签名称字符串。
* @return 创建的Operand。
*/
Operand create_label_operand(const char *name);
/**
* @brief 发射一条三地址码指令,并将其添加到指令列表中。
* @param type 指令类型。
* @param result 结果操作数。
* @param op1 第一个操作数。
* @param op2 第二个操作数。
* @param line C代码行号。
* @param col C代码列号。
*/
void emit_instruction(InstructionType type, Operand result, Operand op1, Operand op2, int line, int col);
/**
* @brief 执行整个中间代码生成过程。
* 遍历语义分析后的AST,生成三地址码。
* @param root AST的根节点 (AST_PROGRAM)。
*/
void generate_intermediate_code(ASTNode *root);
/**
* @brief 递迴地为表达式节点生成中间代码。
* 返回表示表达式结果的Operand (通常是临时变量或常量)。
* @param expr_node 表达式AST节点。
* @return 表达式结果的Operand。
*/
Operand generate_expr_code(ASTNode *expr_node);
/**
* @brief 为语句节点生成中间代码。
* 处理控制流(If, While, Return)和声明。
* @param stmt_node 语句AST节点。
*/
void generate_stmt_code(ASTNode *stmt_node);
/**
* @brief 为函数定义节点生成中间代码。
* @param func_def_node 函数定义AST节点。
*/
void generate_function_code(ASTNode *func_def_node);
// --- 中间代码优化函数声明 (在本系列第四部分实现) ---
void optimize_intermediate_code(Instruction **head);
bool perform_constant_folding(Instruction *head);
bool perform_dead_code_elimination(Instruction **head);
// --- 调试辅助函数 ---
/**
* @brief 辅助函数:将 InstructionType 枚举转换为可读的字符串。
* @param type InstructionType值。
* @return 对应的字符串。
*/
const char *instruction_type_to_string(InstructionType type);
/**
* @brief 辅助函数:将 OperandType 枚举转换为可读的字符串。
* @param type OperandType值。
* @return 对应的字符串。
*/
const char *operand_type_to_string(OperandType type);
/**
* @brief 辅助函数:打印单个Operand的信息。
* @param op 要打印的Operand。
*/
void print_operand(Operand op);
/**
* @brief 打印生成的所有中间代码 (用于调试)。
*/
void print_intermediate_code();
/**
* @brief 释放所有中间代码指令占用的内存。
*/
void free_intermediate_code();
#endif // INTERMEDIATE_CODE_H
6. 从 AST 到 IR 的转换
一旦 AST 经过了语义分析阶段的验证和装饰(即每个表达式节点都带有其推断出的类型,所有标识符都在符号表中注册),我们就可以开始将其转换为中间表示(TAC)。这个过程通常通过遍历 AST 来实现,将高层级的抽象语法结构逐步分解为一系列扁平的 TAC 指令。
6.1 转换过程的核心思想
-
遍历策略: 通常采用深度优先遍历(DFS),特别是后序遍历(Post-order Traversal)。这意味着我们先处理子节点(操作数或子表达式),然后才处理父节点(操作符或复合语句)。这样做的原因在于,在生成父节点的 IR 指令之前,我们需要知道其子表达式的结果,这些结果通常会存储在临时变量中。
-
临时变量的生成: 复杂的表达式会被分解为多个简单的二地址或三地址操作。每个中间结果都需要一个新的临时变量来存储。我们的
new_temp()
函数就是为此目的而设计的。 -
指令的发射: 每当完成一个子表达式的计算或处理一个语句时,就使用
emit_instruction()
函数生成对应的 TAC 指令,并将其添加到全局的 IR 链表中。 -
上下文信息传递: 在生成控制流语句(如
if
,while
)的 IR 时,需要生成跳转指令和标签。这就要求在遍历 AST 时,能够传递或访问到这些标签信息。
6.2 转换函数的角色
我们将实现三个主要的转换函数,它们递归地遍历 AST 的不同部分:
-
generate_expr_code(ASTNode *expr_node)
:-
职责: 递归地为表达式节点生成中间代码。
-
返回值: 返回一个
Operand
结构体,表示该表达式计算结果的“位置”或“值”。这个操作数通常是一个新生成的临时变量,也可能是字面量或直接的标识符。 -
逻辑:
-
字面量(
AST_INT_LITERAL
,AST_FLOAT_LITERAL
等): 直接返回一个Operand
结构体,类型为OP_LITERAL_INT
等,值为字面量本身。 -
标识符(
AST_IDENTIFIER_EXPR
): 从符号表中查找标识符,返回一个Operand
,类型为OP_IDENTIFIER
,值为标识符的名称。 -
二元操作符(
AST_BINARY_OP
):-
递归调用
generate_expr_code()
处理左操作数和右操作数,分别得到op1_result
和op2_result
。 -
生成一个新的临时变量
temp_result = new_temp(expr_node->inferred_type)
。 -
发射一条对应的 IR 指令(如
IR_ADD
,IR_MUL
),将temp_result
作为结果操作数,op1_result
和op2_result
作为操作数。 -
返回
temp_result
。
-
-
一元操作符(
AST_UNARY_OP
):-
递归调用
generate_expr_code()
处理操作数,得到operand_result
。 -
生成新的临时变量
temp_result
。 -
发射 IR 指令(如
IR_NEG
,IR_NOT
),将temp_result
作为结果,operand_result
作为操作数。 -
返回
temp_result
。
-
-
赋值表达式(
AST_ASSIGN_EXPR
):-
递归调用
generate_expr_code()
处理右操作数,得到rhs_result
。 -
左操作数通常是标识符(变量名)。获取其
SymbolEntry
。 -
发射
IR_STORE_VAR
指令,将lhs_var_name
作为结果(存储位置),rhs_result
作为操作数。 -
(C 语言中,赋值表达式的结果是赋值后的值),所以返回
rhs_result
或lhs_var_name
。
-
-
-
-
generate_stmt_code(ASTNode *stmt_node)
:-
职责: 递归地为语句节点生成中间代码。
-
返回值:
void
(语句不产生值)。 -
逻辑:
-
声明语句(
AST_DECLARATION
): 只是在符号表中注册,不直接产生 IR 指令(内存分配在更后期的代码生成阶段处理)。 -
表达式语句(
AST_EXPR_STMT
): 调用generate_expr_code()
处理其内部的表达式。 -
If 语句(
AST_IF_STMT
):-
生成两个新标签:
else_label
和end_if_label
。 -
生成条件表达式的 IR(调用
generate_expr_code()
得到cond_result
)。 -
发射条件跳转指令(如
IR_JEQ cond_result, 0, else_label
),如果条件为假则跳到else_label
。 -
递归调用
generate_stmt_code()
处理then_body
。 -
发射无条件跳转指令
IR_JMP end_if_label
。 -
发射
IR_LABEL else_label
。 -
如果存在
else_body
,递归处理else_body
。 -
发射
IR_LABEL end_if_label
。
-
-
While 循环(
AST_WHILE_STMT
):-
生成三个新标签:
loop_start_label
,loop_body_label
,loop_end_label
。 -
发射
IR_LABEL loop_start_label
。 -
生成条件表达式的 IR(得到
cond_result
)。 -
发射条件跳转
IR_JEQ cond_result, 0, loop_end_label
。 -
发射
IR_LABEL loop_body_label
(可选,可以跳过)。 -
递归处理
loop_body
。 -
发射无条件跳转
IR_JMP loop_start_label
。 -
发射
IR_LABEL loop_end_label
。 -
(需要维护循环的
break_label
和continue_label
栈,以便break
和continue
语句知道跳到哪里)。
-
-
Return 语句(
AST_RETURN_STMT
):-
如果
return
表达式存在,生成其 IR(得到return_value_operand
)。 -
发射
IR_RET return_value_operand
。 -
如果没有表达式(
return;
),发射IR_RET
,操作数OP_NONE
。
-
-
-
-
generate_function_code(ASTNode *func_def_node)
:-
职责: 为函数定义生成中间代码。
-
逻辑:
-
进入函数作用域: 调用
enter_scope()
,并将函数参数插入符号表(参数的偏移量用于后续代码生成)。 -
发射函数标签: 发射
IR_LABEL
指令,标签名为函数名。 -
处理函数体: 遍历函数体内的所有语句(通常是
AST_BLOCK_STMT
),调用generate_stmt_code()
处理每个语句。 -
处理隐式返回: 如果函数不是
void
类型,且函数体末尾没有显式return
语句,可以插入一个默认的return
指令(尽管这是未定义行为,但编译器可以处理)。 -
退出函数作用域: 调用
exit_scope()
。
-
-
-
generate_intermediate_code(ASTNode *root)
:-
职责: 顶层函数,遍历整个程序的 AST (
AST_PROGRAM
节点)。 -
逻辑: 遍历 AST 根节点的所有子节点(通常是函数定义),为每个函数调用
generate_function_code()
。
-
intermediate_code.c
核心逻辑(概念性)
// 文件名称: intermediate_code.c
// 中间代码生成模块的实现
#include "intermediate_code.h"
#include "compiler_errors.h"
#include "symbol_table.h" // 确保符号表可用
Instruction *ir_head = NULL;
Instruction *ir_tail = NULL;
int temp_counter = 0;
int label_counter = 0;
// 为了处理 break/continue,我们需要一个栈来保存最近的循环结束和循环开始标签
// 简化:这里只用全局变量表示当前循环的标签,不支持嵌套循环的break/continue
Operand current_loop_break_label = {0}; // 当前循环的结束标签
Operand current_loop_continue_label = {0}; // 当前循环的继续标签
// --- 辅助函数:创建 Operand ---
Operand create_empty_operand() {
Operand op = {0};
op.type = OP_NONE;
op.data_type = TYPE_UNKNOWN;
op.is_freed = false;
return op;
}
Operand new_temp(DataType type) {
Operand op;
op.type = OP_TEMP;
// 动态生成临时变量名,如 "t0", "t1", ...
op.value.str_val = (char *)malloc(10); // 足够存储 "t<num>\0"
if (!op.value.str_val) { fprintf(stderr, "Out of memory for temp name!\n"); exit(EXIT_FAILURE); }
sprintf(op.value.str_val, "t%d", temp_counter++);
op.data_type = type;
op.is_freed = false;
return op;
}
Operand new_label(const char *prefix) {
Operand op;
op.type = OP_LABEL;
op.value.str_val = (char *)malloc(strlen(prefix) + 10); // 足够存储 "PREFIX<num>\0"
if (!op.value.str_val) { fprintf(stderr, "Out of memory for label name!\n"); exit(EXIT_FAILURE); }
sprintf(op.value.str_val, "%s%d", prefix, label_counter++);
op.data_type = TYPE_UNKNOWN; // 标签没有数据类型
op.is_freed = false;
return op;
}
Operand create_literal_operand_int(long val) {
Operand op;
op.type = OP_LITERAL_INT;
op.value.int_val = val;
op.data_type = TYPE_INT;
op.is_freed = false;
return op;
}
Operand create_literal_operand_float(double val) {
Operand op;
op.type = OP_LITERAL_FLOAT;
op.value.float_val = val;
op.data_type = TYPE_FLOAT;
op.is_freed = false;
return op;
}
Operand create_literal_operand_char(char val) {
Operand op;
op.type = OP_LITERAL_CHAR;
op.value.char_val = val;
op.data_type = TYPE_CHAR;
op.is_freed = false;
return op;
}
Operand create_string_label_operand(const char *str) {
Operand op;
op.type = OP_STRING_LABEL;
op.value.str_val = strdup(str); // 复制字符串字面量
if (!op.value.str_val) { fprintf(stderr, "Out of memory for string label!\n"); exit(EXIT_FAILURE); }
op.data_type = TYPE_UNKNOWN; // 字符串字面量本身没有数据类型,但其内容有
op.is_freed = false;
return op;
}
Operand create_identifier_operand(const char *name, DataType type) {
Operand op;
op.type = OP_IDENTIFIER;
op.value.str_val = strdup(name); // 复制标识符名
if (!op.value.str_val) { fprintf(stderr, "Out of memory for identifier name!\n"); exit(EXIT_FAILURE); }
op.data_type = type;
op.is_freed = false;
return op;
}
Operand create_label_operand(const char *name) {
Operand op;
op.type = OP_LABEL;
op.value.str_val = strdup(name);
if (!op.value.str_val) { fprintf(stderr, "Out of memory for label operand name!\n"); exit(EXIT_FAILURE); }
op.data_type = TYPE_UNKNOWN;
op.is_freed = false;
return op;
}
// --- 中间代码生成核心函数 ---
void init_intermediate_code_generator() {
ir_head = NULL;
ir_tail = NULL;
temp_counter = 0;
label_counter = 0;
current_loop_break_label = create_empty_operand();
current_loop_continue_label = create_empty_operand();
}
void emit_instruction(InstructionType type, Operand result, Operand op1, Operand op2, int line, int col) {
Instruction *instr = (Instruction *)malloc(sizeof(Instruction));
if (!instr) {
fprintf(stderr, "Fatal error: Out of memory for Instruction!\n");
exit(EXIT_FAILURE);
}
instr->type = type;
instr->result = result; // 结构体赋值
instr->op1 = op1;
instr->op2 = op2;
instr->line = line;
instr->col = col;
instr->next = NULL;
instr->is_deleted = false;
if (ir_head == NULL) {
ir_head = instr;
ir_tail = instr;
} else {
ir_tail->next = instr;
ir_tail = instr;
}
}
Operand generate_expr_code(ASTNode *expr_node) {
if (!expr_node) return create_empty_operand();
Operand result_op = create_empty_operand();
Operand op1_val, op2_val;
// 获取表达式的行号和列号,用于生成IR指令
int line = expr_node->token_pos.line;
int col = expr_node->token_pos.col;
switch (expr_node->type) {
case AST_IDENTIFIER_EXPR: {
SymbolEntry *sym = lookup_symbol(expr_node->val.identifier);
if (!sym) {
// 应该已经在语义分析阶段报错,这里只是为了安全
compiler_error(line, col, "Internal error: Undeclared identifier '%s' in IR generation.", expr_node->val.identifier);
return create_empty_operand();
}
result_op = create_identifier_operand(sym->name, sym->type);
break;
}
case AST_INT_LITERAL: {
result_op = create_literal_operand_int(expr_node->val.int_val);
break;
}
case AST_FLOAT_LITERAL: {
result_op = create_literal_operand_float(expr_node->val.float_val);
break;
}
case AST_CHAR_LITERAL: {
result_op = create_literal_operand_char(expr_node->val.char_val);
break;
}
case AST_BINARY_OP: {
op1_val = generate_expr_code(expr_node->left);
op2_val = generate_expr_code(expr_node->right);
result_op = new_temp(expr_node->inferred_type); // 结果存入临时变量
InstructionType ir_type;
switch (expr_node->val.op_type) {
case AST_ADD: ir_type = IR_ADD; break;
case AST_SUB: ir_type = IR_SUB; break;
case AST_MUL: ir_type = IR_MUL; break;
case AST_DIV: ir_type = IR_DIV; break;
case AST_MOD: ir_type = IR_MOD; break;
// 关系操作符
case AST_EQ: ir_type = IR_EQ; break;
case AST_NE: ir_type = IR_NE; break;
case AST_LT: ir_type = IR_LT; break;
case AST_LE: ir_type = IR_LE; break;
case AST_GT: ir_type = IR_GT; break;
case AST_GE: ir_type = IR_GE; break;
// 逻辑操作符
case AST_AND: ir_type = IR_AND; break;
case AST_OR: ir_type = IR_OR; break;
// 位操作符
case AST_BIT_AND: ir_type = IR_BIT_AND; break;
case AST_BIT_OR: ir_type = IR_BIT_OR; break;
case AST_BIT_XOR: ir_type = IR_BIT_XOR; break;
case AST_LSHIFT: ir_type = IR_LSHIFT; break;
case AST_RSHIFT: ir_type = IR_RSHIFT; break;
default:
compiler_error(line, col, "Unsupported binary operator in IR generation.");
return create_empty_operand();
}
emit_instruction(ir_type, result_op, op1_val, op2_val, line, col);
break;
}
case AST_UNARY_OP: {
op1_val = generate_expr_code(expr_node->left);
result_op = new_temp(expr_node->inferred_type);
InstructionType ir_type;
switch (expr_node->val.op_type) {
case AST_NEG: ir_type = IR_NEG; break;
case AST_NOT: ir_type = IR_NOT; break;
case AST_BIT_NOT: ir_type = IR_BIT_NOT; break;
case AST_INC: ir_type = IR_INC; break; // ++x / x++
case AST_DEC: ir_type = IR_DEC; break; // --x / x--
default:
compiler_error(line, col, "Unsupported unary operator in IR generation.");
return create_empty_operand();
}
emit_instruction(ir_type, result_op, op1_val, create_empty_operand(), line, col);
// 对于 ++/-- 运算符,还需要将其结果存回原变量
if (expr_node->val.op_type == AST_INC || expr_node->val.op_type == AST_DEC) {
if (expr_node->left->type == AST_IDENTIFIER_EXPR) {
emit_instruction(IR_STORE_VAR,
create_identifier_operand(expr_node->left->val.identifier, expr_node->left->inferred_type),
result_op, create_empty_operand(), line, col);
} else {
// ++/-- 只能用于可修改的左值
compiler_error(line, col, "Increment/decrement can only be applied to a modifiable l-value.");
}
}
break;
}
case AST_ASSIGN_EXPR: {
// 右侧表达式求值
op2_val = generate_expr_code(expr_node->right); // op2_val 是右侧表达式的值
// 左侧必须是标识符表达式
if (expr_node->left->type != AST_IDENTIFIER_EXPR) {
compiler_error(line, col, "Left-hand side of assignment must be an assignable variable.");
return create_empty_operand();
}
// 左侧是变量名
op1_val = create_identifier_operand(expr_node->left->val.identifier, expr_node->left->inferred_type);
// 发射 STORE_VAR 指令:op1_val (变量名) = op2_val (值)
emit_instruction(IR_STORE_VAR, op1_val, op2_val, create_empty_operand(), line, col);
result_op = op2_val; // 赋值表达式的结果通常是赋值后的值
break;
}
case AST_FUNC_CALL_EXPR: {
SymbolEntry *func_sym = lookup_symbol(expr_node->val.func_call.func_name);
if (!func_sym) {
compiler_error(line, col, "Internal error: Function '%s' not found in symbol table during IR generation.", expr_node->val.func_call.func_name);
return create_empty_operand();
}
// 1. 发射参数传递指令 (从右到左或从左到右,取决于约定,这里简化为按序)
for (int i = 0; i < expr_node->val.func_call.num_args; i++) {
Operand arg_op = generate_expr_code(expr_node->val.func_call.args[i]);
emit_instruction(IR_PARAM, arg_op, create_empty_operand(), create_empty_operand(), line, col);
}
// 2. 发射 CALL 指令
result_op = new_temp(func_sym->details.func_info.return_type); // 函数调用的结果存入临时变量
emit_instruction(IR_CALL, result_op, create_identifier_operand(func_sym->name, func_sym->type),
create_literal_operand_int(func_sym->details.func_info.num_params), line, col);
break;
}
// TODO: 其他表达式类型,如数组访问 AST_ARRAY_ACCESS, 类型转换 AST_CAST_EXPR
default:
compiler_error(line, col, "Unhandled expression AST type for IR generation: %s", ast_node_type_to_string(expr_node->type));
return create_empty_operand();
}
return result_op;
}
void generate_stmt_code(ASTNode *stmt_node) {
if (!stmt_node) return;
int line = stmt_node->token_pos.line;
int col = stmt_node->token_pos.col;
switch (stmt_node->type) {
case AST_VAR_DECL: {
// 变量声明本身不直接生成IR指令,内存分配在后端处理
// 但如果声明带有初始化,则生成赋值指令
if (stmt_node->val.var_decl.initializer) {
Operand var_op = create_identifier_operand(stmt_node->val.var_decl.var_name, stmt_node->inferred_type);
Operand init_expr_op = generate_expr_code(stmt_node->val.var_decl.initializer);
emit_instruction(IR_STORE_VAR, var_op, init_expr_op, create_empty_operand(), line, col);
}
break;
}
case AST_EXPR_STMT: {
// 表达式语句,只需要生成表达式的IR
generate_expr_code(stmt_node->left);
break;
}
case AST_BLOCK_STMT: {
// 进入新的作用域
enter_scope();
ASTNode *current_child = stmt_node->val.block_stmt.statements;
while (current_child) {
generate_stmt_code(current_child);
current_child = current_child->next_sibling;
}
// 退出作用域
exit_scope(line, col); // 使用块语句的行号列号
break;
}
case AST_IF_STMT: {
Operand else_label = new_label("L_ELSE");
Operand end_if_label = new_label("L_ENDIF");
Operand cond_op = generate_expr_code(stmt_node->val.if_stmt.condition);
// IF 条件为假,跳转到 else_label
emit_instruction(IR_JEQ, cond_op, create_literal_operand_int(0), else_label, line, col);
// 处理 then 块
generate_stmt_code(stmt_node->val.if_stmt.then_body);
// 如果有 else,then 块执行完后跳过 else 块
if (stmt_node->val.if_stmt.else_body) {
emit_instruction(IR_JMP, end_if_label, create_empty_operand(), create_empty_operand(), line, col);
}
emit_instruction(IR_LABEL, else_label, create_empty_operand(), create_empty_operand(), line, col);
// 处理 else 块
if (stmt_node->val.if_stmt.else_body) {
generate_stmt_code(stmt_node->val.if_stmt.else_body);
}
emit_instruction(IR_LABEL, end_if_label, create_empty_operand(), create_empty_operand(), line, col);
break;
}
case AST_WHILE_STMT: {
Operand loop_start_label = new_label("L_WHILE_START");
Operand loop_body_label = new_label("L_WHILE_BODY"); // 实际可以省略
Operand loop_end_label = new_label("L_WHILE_END");
// 保存当前循环的 break/continue 标签
Operand old_break_label = current_loop_break_label;
Operand old_continue_label = current_loop_continue_label;
current_loop_break_label = loop_end_label;
current_loop_continue_label = loop_start_label; // continue跳到条件检查
emit_instruction(IR_LABEL, loop_start_label, create_empty_operand(), create_empty_operand(), line, col);
Operand cond_op = generate_expr_code(stmt_node->val.while_stmt.condition);
// WHILE 条件为假,跳出循环
emit_instruction(IR_JEQ, cond_op, create_literal_operand_int(0), loop_end_label, line, col);
// 处理循环体
generate_stmt_code(stmt_node->val.while_stmt.body);
// 循环体结束后无条件跳回循环开始处进行下一次条件检查
emit_instruction(IR_JMP, loop_start_label, create_empty_operand(), create_empty_operand(), line, col);
emit_instruction(IR_LABEL, loop_end_label, create_empty_operand(), create_empty_operand(), line, col);
// 恢复旧的 break/continue 标签
current_loop_break_label = old_break_label;
current_loop_continue_label = old_continue_label;
break;
}
case AST_BREAK_STMT: {
if (current_loop_break_label.type == OP_NONE) {
compiler_error(line, col, "break statement not within a loop or switch.");
return;
}
emit_instruction(IR_JMP, current_loop_break_label, create_empty_operand(), create_empty_operand(), line, col);
break;
}
case AST_CONTINUE_STMT: {
if (current_loop_continue_label.type == OP_NONE) {
compiler_error(line, col, "continue statement not within a loop.");
return;
}
emit_instruction(IR_JMP, current_loop_continue_label, create_empty_operand(), create_empty_operand(), line, col);
break;
}
case AST_RETURN_STMT: {
if (stmt_node->left) { // 有返回值
Operand ret_val_op = generate_expr_code(stmt_node->left);
emit_instruction(IR_RET, ret_val_op, create_empty_operand(), create_empty_operand(), line, col);
} else { // 无返回值
emit_instruction(IR_RET, create_empty_operand(), create_empty_operand(), create_empty_operand(), line, col);
}
break;
}
// TODO: 其他语句类型,如 AST_FOR_STMT, AST_SWITCH_STMT, AST_FUNCTION_DECL (仅声明)
default:
compiler_error(line, col, "Unhandled statement AST type for IR generation: %s", ast_node_type_to_string(stmt_node->type));
break;
}
}
void generate_function_code(ASTNode *func_def_node) {
if (!func_def_node || func_def_node->type != AST_FUNC_DEF) {
compiler_error(0, 0, "Internal error: Expected function definition AST node.");
return;
}
// 获取函数在符号表中的条目
SymbolEntry *func_sym = lookup_symbol_in_current_scope(func_def_node->val.func_def.func_name);
if (!func_sym) {
compiler_error(func_def_node->token_pos.line, func_def_node->token_pos.col, "Internal error: Function '%s' not found in current scope during IR generation.", func_def_node->val.func_def.func_name);
return;
}
// 1. 发射函数入口标签
emit_instruction(IR_LABEL, create_label_operand(func_sym->name), create_empty_operand(), create_empty_operand(), func_def_node->token_pos.line, func_def_node->token_pos.col);
// 2. 进入函数作用域 (参数已在语义分析时插入,这里仅表示进入新栈帧)
// 通常在语法/语义分析时,函数体之前就已经enter_scope()了,这里是IR生成时,再次“定位”到该作用域
// 我们假定 generate_intermediate_code 在处理函数时,已经处理了enter/exit_scope
// 3. 处理函数体内的语句
ASTNode *current_stmt = func_def_node->val.func_def.body; // 函数体通常是一个 AST_BLOCK_STMT
generate_stmt_code(current_stmt);
// 4. 处理隐式返回 (如果需要且函数非void)
if (func_sym->details.func_info.return_type != TYPE_VOID) {
// 简单处理:如果函数没有显式 return,我们添加一个返回 0 的指令
// 这是为了防止某些控制流分支没有 return 语句而导致错误。
// 严格的编译器会在语义分析阶段报告“控制流到达非 void 函数末尾”的警告。
bool has_explicit_return = false; // TODO: 复杂分析判断是否有显式return
// if (!has_explicit_return) {
// emit_instruction(IR_RET, create_literal_operand_int(0), create_empty_operand(), create_empty_operand(), func_def_node->token_pos.line, func_def_node->token_pos.col);
// }
} else {
// void 函数,确保最后有一个返回指令
emit_instruction(IR_RET, create_empty_operand(), create_empty_operand(), create_empty_operand(), func_def_node->token_pos.line, func_def_node->token_pos.col);
}
// 5. 退出函数作用域 (在 generate_stmt_code 中,如果 body 是 AST_BLOCK_STMT,会自动处理 exit_scope)
}
void generate_intermediate_code(ASTNode *root) {
if (!root || root->type != AST_PROGRAM) {
compiler_error(0, 0, "Internal error: Expected AST_PROGRAM as root for IR generation.");
return;
}
// 假设全局作用域已由 init_symbol_table 创建并进入
// 遍历所有顶层声明(通常是函数定义或全局变量声明)
ASTNode *current_node = root->val.program.declarations;
while (current_node) {
if (current_node->type == AST_FUNC_DEF) {
// 对于每个函数定义,生成其IR
generate_function_code(current_node);
} else if (current_node->type == AST_VAR_DECL) {
// 对于全局变量声明,如果带初始化,也需要生成IR
generate_stmt_code(current_node); // 复用语句生成逻辑
}
// TODO: 处理其他顶层声明,如 struct 定义等,它们不直接生成IR指令
current_node = current_node->next_sibling;
}
}
// --- 调试辅助函数实现 ---
const char *instruction_type_to_string(InstructionType type) {
switch (type) {
case IR_ADD: return "ADD"; case IR_SUB: return "SUB"; case IR_MUL: return "MUL";
case IR_DIV: return "DIV"; case IR_MOD: return "MOD"; case IR_NEG: return "NEG";
case IR_NOT: return "NOT"; case IR_BIT_NOT: return "BIT_NOT";
case IR_INC: return "INC"; case IR_DEC: return "DEC";
case IR_ASSIGN: return "ASSIGN";
case IR_EQ: return "EQ"; case IR_NE: return "NE"; case IR_LT: return "LT";
case IR_LE: return "LE"; case IR_GT: return "GT"; case IR_GE: return "GE";
case IR_AND: return "AND"; case IR_OR: return "OR";
case IR_BIT_AND: return "BIT_AND"; case IR_BIT_OR: return "BIT_OR"; case IR_BIT_XOR: return "BIT_XOR";
case IR_LSHIFT: return "LSHIFT"; case IR_RSHIFT: return "RSHIFT";
case IR_LABEL: return "LABEL"; case IR_JMP: return "JMP";
case IR_JEQ: return "JEQ"; case IR_JNE: return "JNE"; case IR_JLT: return "JLT";
case IR_JLE: return "JLE"; case IR_JGT: return "JGT"; case IR_JGE: return "JGE";
case IR_PARAM: return "PARAM"; case IR_CALL: return "CALL"; case IR_RET: return "RET";
case IR_LOAD_VAR: return "LOAD_VAR"; case IR_STORE_VAR: return "STORE_VAR";
case IR_BREAK: return "BREAK"; case IR_CONTINUE: return "CONTINUE";
default: return "UNKNOWN_IR_TYPE";
}
}
const char *operand_type_to_string(OperandType type) {
switch (type) {
case OP_NONE: return "NONE";
case OP_TEMP: return "TEMP";
case OP_LITERAL_INT: return "LIT_INT";
case OP_LITERAL_FLOAT: return "LIT_FLOAT";
case OP_LITERAL_CHAR: return "LIT_CHAR";
case OP_STRING_LABEL: return "STR_LABEL";
case OP_IDENTIFIER: return "IDENT";
case OP_LABEL: return "LABEL_REF";
default: return "UNKNOWN_OP_TYPE";
}
}
void print_operand(Operand op) {
switch (op.type) {
case OP_TEMP:
case OP_IDENTIFIER:
case OP_STRING_LABEL:
case OP_LABEL:
fprintf(stderr, "%s (%s)", op.value.str_val, data_type_to_string(op.data_type));
break;
case OP_LITERAL_INT:
fprintf(stderr, "%ld (INT)", op.value.int_val);
break;
case OP_LITERAL_FLOAT:
fprintf(stderr, "%f (FLOAT)", op.value.float_val);
break;
case OP_LITERAL_CHAR:
fprintf(stderr, "'%c' (CHAR)", op.value.char_val);
break;
case OP_NONE:
default:
fprintf(stderr, "NONE");
break;
}
}
void print_intermediate_code() {
fprintf(stderr, "\n--- Intermediate Code (Three-Address Code) ---\n");
Instruction *current = ir_head;
int instr_idx = 0;
while (current != NULL) {
if (current->is_deleted) { // 跳过已删除的指令
current = current->next;
continue;
}
fprintf(stderr, "I%03d [%d:%d]: ", instr_idx++, current->line, current->col);
fprintf(stderr, "%s ", instruction_type_to_string(current->type));
// 根据指令类型打印操作数
switch (current->type) {
case IR_ADD: case IR_SUB: case IR_MUL: case IR_DIV: case IR_MOD:
case IR_AND: case IR_OR: case IR_BIT_AND: case IR_BIT_OR: case IR_BIT_XOR:
case IR_LSHIFT: case IR_RSHIFT:
print_operand(current->result);
fprintf(stderr, " = ");
print_operand(current->op1);
fprintf(stderr, " %s ", instruction_type_to_string(current->type)); // 再次打印运算符
print_operand(current->op2);
break;
case IR_NEG: case IR_NOT: case IR_BIT_NOT:
print_operand(current->result);
fprintf(stderr, " = %s ", instruction_type_to_string(current->type));
print_operand(current->op1);
break;
case IR_INC: case IR_DEC:
print_operand(current->result);
fprintf(stderr, " = ");
print_operand(current->op1);
fprintf(stderr, " %s ", instruction_type_to_string(current->type));
break;
case IR_ASSIGN: // result = op1
print_operand(current->result);
fprintf(stderr, " = ");
print_operand(current->op1);
break;
case IR_STORE_VAR: // var = op1
print_operand(current->result); // 变量名
fprintf(stderr, " = ");
print_operand(current->op1); // 值
break;
case IR_LOAD_VAR: // result = var
print_operand(current->result);
fprintf(stderr, " = ");
print_operand(current->op1);
break;
case IR_LABEL:
print_operand(current->result); // 标签本身
fprintf(stderr, ":");
break;
case IR_JMP:
fprintf(stderr, "TO ");
print_operand(current->result); // 跳转目标标签
break;
case IR_JEQ: case IR_JNE: case IR_JLT: case IR_JLE: case IR_JGT: case IR_JGE:
fprintf(stderr, "IF ");
print_operand(current->op1);
fprintf(stderr, " %s ", instruction_type_to_string(current->type)); // 关系运算符
print_operand(current->op2);
fprintf(stderr, " GOTO ");
print_operand(current->result); // 跳转目标标签
break;
case IR_PARAM:
fprintf(stderr, " ");
print_operand(current->result); // 参数值
break;
case IR_CALL: // CALL func_name, num_args, result_temp
print_operand(current->result); // 结果临时变量 (如果函数有返回值)
fprintf(stderr, " = CALL ");
print_operand(current->op1); // 函数名
fprintf(stderr, ", %ld args", current->op2.value.int_val); // 参数数量
break;
case IR_RET:
fprintf(stderr, " ");
print_operand(current->result); // 返回值 (如果是void则为NONE)
break;
case IR_BREAK:
fprintf(stderr, " (JMP to current_loop_break_label)");
break;
case IR_CONTINUE:
fprintf(stderr, " (JMP to current_loop_continue_label)");
break;
default:
fprintf(stderr, "UNKNOWN_INSTRUCTION");
break;
}
fprintf(stderr, "\n");
current = current->next;
}
fprintf(stderr, "--------------------------------------------\n");
}
void free_operand_content(Operand op) {
if (op.type == OP_TEMP || op.type == OP_IDENTIFIER ||
op.type == OP_STRING_LABEL || op.type == OP_LABEL) {
if (op.value.str_val && !op.is_freed) { // 避免双重释放
free(op.value.str_val);
op.is_freed = true; // 标记为已释放
}
}
}
void free_intermediate_code() {
Instruction *current = ir_head;
while (current != NULL) {
Instruction *next_instr = current->next;
free_operand_content(current->result);
free_operand_content(current->op1);
free_operand_content(current->op2);
free(current);
current = next_instr;
}
ir_head = NULL;
ir_tail = NULL;
temp_counter = 0;
label_counter = 0;
// 确保全局的break/continue标签也是空操作数,避免后续误用
current_loop_break_label = create_empty_operand();
current_loop_continue_label = create_empty_operand();
}
// TODO: 提供 ast_node_type_to_string 辅助函数,需要 AST.h 提供相关定义
// 假设 AST.h 中有 ASTNodeType 的字符串转换函数
const char *ast_node_type_to_string(ASTNodeType type) {
switch(type) {
case AST_PROGRAM: return "PROGRAM";
case AST_FUNC_DEF: return "FUNC_DEF";
case AST_VAR_DECL: return "VAR_DECL";
case AST_BLOCK_STMT: return "BLOCK_STMT";
case AST_EXPR_STMT: return "EXPR_STMT";
case AST_RETURN_STMT: return "RETURN_STMT";
case AST_IF_STMT: return "IF_STMT";
case AST_WHILE_STMT: return "WHILE_STMT";
case AST_BREAK_STMT: return "BREAK_STMT";
case AST_CONTINUE_STMT: return "CONTINUE_STMT";
case AST_INT_LITERAL: return "INT_LITERAL";
case AST_FLOAT_LITERAL: return "FLOAT_LITERAL";
case AST_CHAR_LITERAL: return "CHAR_LITERAL";
case AST_IDENTIFIER_EXPR: return "IDENTIFIER_EXPR";
case AST_BINARY_OP: return "BINARY_OP";
case AST_UNARY_OP: return "UNARY_OP";
case AST_ASSIGN_EXPR: return "ASSIGN_EXPR";
case AST_FUNC_CALL_EXPR: return "FUNC_CALL_EXPR";
// Binary Operators
case AST_ADD: return "ADD_OP"; case AST_SUB: return "SUB_OP"; case AST_MUL: return "MUL_OP";
case AST_DIV: return "DIV_OP"; case AST_MOD: return "MOD_OP";
case AST_EQ: return "EQ_OP"; case AST_NE: return "NE_OP"; case AST_LT: return "LT_OP";
case AST_LE: return "LE_OP"; case AST_GT: return "GT_OP"; case AST_GE: return "GE_OP";
case AST_AND: return "AND_OP"; case AST_OR: return "OR_OP";
case AST_BIT_AND: return "BIT_AND_OP"; case AST_BIT_OR: return "BIT_OR_OP"; case AST_BIT_XOR: return "BIT_XOR_OP";
case AST_LSHIFT: return "LSHIFT_OP"; case AST_RSHIFT: return "RSHIFT_OP";
// Unary Operators
case AST_NEG: return "NEG_OP"; case AST_NOT: return "NOT_OP"; case AST_BIT_NOT: return "BIT_NOT_OP";
case AST_INC: return "INC_OP"; case AST_DEC: return "DEC_OP";
default: return "UNKNOWN_AST_TYPE";
}
}
解释与扩展:
-
generate_expr_code()
: 递归地为每个表达式生成 IR。它始终返回一个Operand
,代表表达式计算的结果。对于复杂的二元/一元运算,它会先生成子表达式的 IR,然后创建一个新的临时变量来存储当前运算的结果,并发射相应的 IR 指令。 -
generate_stmt_code()
: 递归地为各种语句生成 IR。例如,if
语句会涉及条件判断和跳转指令,while
循环会涉及循环入口、条件判断、循环体和循环结束的标签与跳转。为了简化,break
和continue
暂时使用全局变量来存储当前循环的标签,这在多层嵌套循环中需要改为栈结构来正确管理。 -
generate_function_code()
: 处理函数定义。它会发射函数入口标签,然后递归地处理函数体内的所有语句。最后,它会确保函数有一个return
指令(即使是void
函数)。 -
emit_instruction()
: 这个函数负责将新生成的Instruction
添加到全局的ir_head
和ir_tail
链表中。 -
print_intermediate_code()
: 提供了一个调试辅助函数,可以清晰地打印出生成的三地址码序列,这对于理解编译器内部运作和调试非常重要。 -
内存管理:
free_intermediate_code()
负责释放所有 IR 指令占用的内存,包括指令结构体本身和内部Operand
中动态分配的字符串(如临时变量名、标识符名、标签名)。这里的is_freed
标记在Operand
结构体中,是为了避免在优化过程中(例如,一个操作数被复制到多个地方,但其底层字符串只分配一次)发生双重释放,这将在下一部分优化时体现其作用。
通过以上语义分析和 IR 生成的步骤,我们的编译器就能够:
-
检查程序的“意义”: 确保所有变量都声明了,类型匹配,函数调用正确。
-
收集上下文信息: 将变量的类型、作用域、偏移量等信息存储在符号表中,并装饰到 AST 节点上。
-
转换到中间表示: 将语义正确的 AST 转化为一系列扁平、机器无关的三地址码指令,为后续的代码优化和代码生成奠定基础。
总结与展望 (第三部分)
在这一部分中,我们完成了编译器前端到中端的关键过渡。我们深入探讨了语义分析的职责,它如何作为编译器的“大脑”,理解程序的内在含义,并识别出那些词法和语法分析无法捕捉的逻辑错误。符号表作为语义分析的核心数据结构,其构建、管理和作用域规则是实现正确标识符解析和错误检查的关键。我们还详细探讨了 C 语言的类型系统和类型检查机制,理解了类型推断、隐式转换和兼容性规则,并给出了实际的类型检查代码示例。
随后,我们引入了**中间表示(IR)**的概念,强调了它作为连接编译器前后端的桥梁的重要性,以及它如何为代码优化提供一个理想的、机器无关的抽象舞台。我们聚焦于常用的 三地址码(TAC),理解了其扁平、显式和简洁的特性,及其对后续分析和优化的优势。
最后,我们通过详细的代码实现和解释,演示了如何从语义分析后的抽象语法树(AST)递归地遍历,并生成对应的三地址码序列。这个过程将高层抽象的程序结构分解为一系列原子操作,为后续的性能优化和最终代码生成铺平了道路。
至此,我们的编译器已经能够将 C 语言源代码从原始文本转化为一个语义正确、结构清晰且便于处理的中间表示。在接下来的第四部分,我们将迎来编译器的“性能魔法”——代码优化(Code Optimization)。我们将在这些生成的三地址码基础上,实现一系列常见的优化技术,让我们的编译器能够生成更小、更快、更高效的机器代码。
详解计算机组成原理:从正确到高效——编译器的性能魔法(第四部分)
引言:从正确到高效——编译器的性能魔法
在系列的前三部分中,我们逐步揭示了C语言编译器的内部工作流程:
-
第一部分 宏观介绍了C语言、汇编语言以及编译原理,让您对它们的关系有了初步认识。
-
第二部分 深入探讨了编译器前端的词法分析和语法分析,我们学习了如何将原始C代码转化为结构化的抽象语法树(AST)。
-
第三部分 则聚焦于语义分析和中间代码生成。我们引入了符号表进行类型检查和作用域管理,并将语义正确、类型匹配的AST转化为易于处理的三地址码(Three-Address Code, TAC)形式的中间表示(IR)。
至此,我们的编译器已经能够将C源代码正确地翻译成一种机器无关的底层表示。但“正确”不等于“高效”。考虑以下C语言代码:
int x = 10 + 20;
int y = x * 2;
for (int i = 0; i < 100; i++) {
int temp = a * b; // 假设a和b在循环内不变
sum += temp;
}
一个直接翻译的编译器可能会生成许多冗余或低效的指令:
-
10 + 20
这样的常量表达式,如果不在编译时就算好,而在运行时才计算,就会浪费CPU周期。 -
在循环中,如果
a * b
的结果在每次迭代中都相同,那么每次都重新计算它也是浪费。
这时候,编译器的“性能魔法”——**代码优化(Code Optimization)**就登场了。代码优化的目标是在不改变程序外部行为的前提下,通过各种转换和改写,使得程序执行得更快、消耗更少的内存、甚至降低能耗。
本部分,我们将深入探讨代码优化的原理与常用技术,并在此前生成的三地址码基础上,实现一些简单而有效的优化算法。我们将涵盖以下内容:
-
为什么要优化?优化目标与挑战: 理解代码优化的核心目的,以及实现优化所面临的复杂性与权衡。
-
中间表示:优化的最佳舞台: 强调IR在优化阶段的重要性。
-
常见的机器无关优化技术: 详细讲解数种经典的优化算法,包括常量折叠、死代码消除和公共子表达式消除。
-
基于三地址码的优化实现: 演示如何在我们设计的三地址码上实现这些优化。
-
优化代码整合与完整示例: 将优化器整合到整个编译流程中,并展示优化前后IR的对比。
通过本部分的学习,您将能够:
-
掌握代码优化的核心概念和几种常用技术。
-
理解优化器如何通过分析和转换IR来提升程序性能。
-
亲手实现一个简化的代码优化器,为您的编译器赋予“性能魔法”。
1. 为什么要优化?优化目标与挑战
代码优化是编译器的一个核心组成部分,其目的是提高生成代码的效率。
1.1 优化的主要目标
代码优化的目标是多方面的,通常包括:
-
提高执行速度(Execution Speed): 这是最常见的也是最重要的目标。通过减少指令数量、优化指令顺序、利用CPU特性(如流水线、缓存)、减少内存访问等方式,使程序运行更快。例如,一个高效的算法在编译器的优化下可以更快地完成任务。
-
减少代码大小(Code Size): 对于资源受限的嵌入式系统、移动应用(减小安装包体积)或网络传输(减少下载时间)等场景,缩小可执行文件的大小至关重要。优化可以通过消除冗余代码、使用更紧凑的指令序列(例如,用位运算代替乘除法)来实现。
-
降低功耗(Power Consumption): 尤其在电池供电的设备(如手机、物联网设备)上,减少CPU周期、减少内存和I/O设备的活动可以显著降低能耗,延长电池寿命。某些优化可以通过减少CPU的繁忙程度,使其更快进入低功耗状态。
-
减少内存占用(Memory Usage): 优化可以减少程序在运行时所需的内存,例如通过更智能的数据结构布局、重用内存空间、避免不必要的临时变量或数据复制。这对于内存受限的环境尤为重要,也可以减少缓存失效,间接提高速度。
这些目标有时是相互冲突的。例如,某些优化可能以增加代码大小为代价来换取执行速度(如循环展开),反之亦然。因此,现代编译器通常提供不同的优化级别(如 -O1
, -O2
, -O3
, -Os
,其中 -Os
通常侧重于优化代码大小),让开发者根据需求进行权衡。
1.2 优化面临的挑战与权衡
实现高效的代碼优化器是一个极具挑战性的任务:
-
正确性(Correctness): 优化器必须保证转换后的代码与原始代码在逻辑上完全等价,即不能改变程序的外部行为(包括错误行为,例如除零错误或段错误)。这是优化器的最高原则。一个错误的优化会通过引入新的Bug来破坏程序的正确性。
-
复杂性(Complexity): 许多高级优化需要复杂的数据流分析、控制流分析、别名分析等。这些分析需要遍历程序的IR,构建复杂的图结构,并执行迭代算法。这使得优化器的设计和实现非常复杂,且难以完全避免所有潜在的Bug。
-
编译时间(Compilation Time): 优化是一个计算密集型过程,需要消耗大量的计算资源和时间。一个过度优化的编译过程可能会导致编译时间过长,这在开发阶段(需要频繁编译测试)是不可接受的。因此,优化器需要在优化效果和编译时间之间找到一个平衡点。
-
通用性(Generality): 理想的优化算法应该对各种程序模式都有效,而不是仅仅针对特定场景或语言特性。设计出具有高度通用性且能显著提升性能的优化算法是一大挑战。
-
机器相关性(Machine Dependence): 尽管许多优化是机器无关的(在IR层面进行),但有些关键的优化(如寄存器分配、指令调度、利用特定的CPU指令集扩展)是高度机器相关的,需要深入了解目标CPU的微架构特性(如流水线深度、乱序执行能力、缓存结构)。这增加了优化器的复杂性和维护成本。
-
不可预测性(Unpredictability): 有时,一个看似“优化”的转换在理论上是有效的,但在实际运行时反而会因为某些难以预测的因素(例如,CPU缓存行为的微妙变化、分支预测器的误判、或现代CPU的复杂乱序执行机制)而导致性能下降。这使得优化效果的评估变得困难。
-
程序行为改变风险: 某些“激进”的优化可能会在一些边缘情况下改变程序的外部行为(例如,浮点运算的精度变化、对内存访问顺序的重新排列)。编译器通常会提供选项来控制这些可能改变行为的优化。
正因为这些挑战,代码优化是编译器领域中最活跃和研究最深入的领域之一。
2. 中间表示:优化的最佳舞台
在第三部分,我们已经强调了**中间表示(Intermediate Representation, IR)**在编译器中的重要性。对于代码优化而言,IR更是其“最佳舞台”。
2.1 IR如何助力优化?
-
抽象层次适中:
-
比源语言更接近机器: IR去除了源语言中的语法细节、高层抽象(如复杂的表达式、循环和条件语句的嵌套结构),将程序分解为更小的原子操作。这使得优化器可以更聚焦于数据流和控制流的逻辑,而不必处理源语言的复杂语法。
-
比机器码更抽象: IR仍然是机器无关的。这意味着我们可以在IR层面执行大量的通用优化,而无需针对每个目标CPU架构重新实现。这种独立性大大提高了编译器的可移植性,一份优化代码可以服务于多种目标平台。
-
-
明确的语义: IR的语义通常非常明确,每个操作(如加法、赋值、跳转)的含义都清晰定义,没有歧义。这有助於优化器进行精确的數據流分析(数据从哪里来,到哪里去)和控制流分析(程序可能有哪些执行路径),为后续的优化转换提供了坚实的基础。
-
易于分析和转换:
-
分析: IR(尤其是三地址码)的简单、规范化结构使得编译器可以更容易地分析数据如何流动、变量的生命周期、控制流的路徑(通过基本块和控制流图)。这种结构化表示是所有复杂分析的基础。
-
转换: IR的结构化特性使得优化器可以方便、安全地插入、删除、移动或替换指令。由于每条指令都非常原子化,优化器可以更精确地进行局部或全局的重写,而无需担心破坏整个程序的结构或逻辑。
-
2.2 三地址码作为优化目标
我们此前使用的**三地址码(TAC)**是一种非常适合进行优化的IR。其 result = operand1 operator operand2
或 result = operator operand1
的简洁形式,使得:
-
数据流分析变得直观: 每个操作的输入和输出都清晰可见,易于追踪数据的来源和去向。
-
公共子表达式识别变得容易: 通过比较指令的操作类型和操作数,可以相对容易地识别出重复计算的表达式。
-
死代码识别变得可行: 通过追溯临时变量和最终变量的用途,可以判断哪些计算结果或变量赋值是“无用”的。
3. 常见的机器无关优化技术
代码优化技术种类繁多,我们可以将它们分为多個类别。在这里,我们将重点介绍一些对性能提升显著且相对容易在TAC层面理解和实现机器无关优化。
3.1 常量折叠(Constant Folding)
-
原理: 在编译时计算出所有常量表达式的值,并将其替换为实际的常量。这样可以避免在程序运行时重复执行这些计算,直接使用最终结果。
-
适用范围: 算术运算、逻辑运算、位运算、关系运算等,只要所有操作数都是常量。
-
示例:
-
C语言代码:
int x = 10 + 20;
-
优化前TAC:
ADD t1, 10, 20 ASSIGN x, t1
-
优化后TAC:
ASSIGN x, 30
(这里,
ADD t1, 10, 20
被消除了,其结果直接用于赋值给x
)。 -
3.2 死代码消除(Dead Code Elimination, DCE)
-
原理: 识别并删除那些永远不会被执行(称为不可达代码 Reachable Code)或其计算结果从未使用过(称为无用代码 Useless Code)的代碼。移除死代码可以减小最终可执行文件的大小,并可能为其他优化(如常量传播)创造机会。
-
示例:
-
C语言代码:
int x = 10; int y = 20; y = x + 5; // x的值没有被后续代码使用 return y;
-
优化前TAC(假设直接翻译):
ASSIGN x, 10 ASSIGN y, 20 ADD t1, x, 5 ASSIGN y, t1 RET y
-
优化后TAC(假设DCE足够智能):
ASSIGN y, 20 // ASSIGN x, 10 被移除,因为x在t1 = x + 5之后没有被再次使用 // 进一步,如果编译器能够推断x+5的结果,可能变成: // ASSIGN y, 25 RET y
在这个例子中,对
x
的初始赋值ASSIGN x, 10
在ADD t1, x, 5
之后x
不再被读取,那么这条对x
的赋值就是死代码。同样,t1
是一个临时变量,如果t1
在ASSIGN y, t1
之后再也没有被用作操作数,那么ADD t1, x, 5
(以及它产生的t1
)就是无用代码。
-
-
实现挑战: 准确判断代碼是否“死”需要复杂的数据流分析,特别是活跃性分析(Liveness Analysis),以确定变量在程序的哪些点是“活跃的”(即其值可能在未来被使用)。对于本简化的实例,我们将实现一个最基础的DCE,例如移除对临时变量的赋值,如果该临时变量在此之后再也没有被用作操作数。
3.3 公共子表达式消除(Common Subexpression Elimination, CSE)
-
原理: 识别程序中重复计算的表达式。如果这些表达式在计算之间,其操作数的值没有被改变,则只计算一次,将结果存储在临时变量中,后继再使用该临时变量,避免重复计算。
-
示例:
-
C语言代码:
result1 = (a * b) + c; result2 = (a * b) + d;
-
优化前TAC:
MUL t1, a, b ADD t2, t1, c ASSIGN result1, t2 MUL t3, a, b // 重复计算 ADD t4, t3, d ASSIGN result2, t4
-
优化后TAC:
MUL t1, a, b // 计算一次 a * b ADD t2, t1, c ASSIGN result1, t2 ADD t4, t1, d // 重用 t1 的结果 ASSIGN result2, t4
-
-
实现挑战: 需要在局部(基本块内)或全局(整个程序)范围内跟踪表达式及其值,并判断其值是否在重新计算前被改变。这通常涉及值编号(Value Numbering)或更复杂的可达定义分析和可用表达式分析(Available Expression Analysis)。对于本简化的实现,我们将专注于常量折叠和死代码消除,CSE的完全实现超出了本次演示的范围。
3.4 循环优化(Loop Optimizations)
循环是程序中执行次数最多的部分,因此对循环的优化往往能带来巨大的性能提升。
-
循环不变代码外提(Loop-Invariant Code Motion, LICM):
-
原理: 将循环体内那些每次迭代计算结果都相同的表达式(即循环不变量)移到循环外面,使其只计算一次。
-
示例: C代码:
for (int i = 0; i < N; i++) { x = y * z; // y和z在循环内不变 // ... }
-
优化前TAC:
... LABEL L_LOOP_START ... MUL t1, y, z ASSIGN x, t1 ...
-
优化后TAC:
MUL t1, y, z // 移到循环外 ... LABEL L_LOOP_START ... ASSIGN x, t1 // 循环内只赋值 ...
-
实现挑战: 需要精确判断哪些表达式是循环不变量,且移动后不会改变程序的语义(如副作用)。
-
-
强度削减(Strength Reduction):
-
原理: 将循环中代价较大的操作(如乘法或除法)替换为代价较小的等价操作(如加法或位移)。这通常应用于循环的归纳变量(Induction Variables)。
-
示例: C代码:
for (int i = 0; i < N; i++) { array[i * 4] = 0; // i*4 每次迭代都计算 }
-
优化前TAC:
... LABEL L_LOOP_START: MUL t1, i, 4 STORE [array + t1], 0 ...
-
优化后TAC:
ASSIGN t_index, 0 // 引入新的归纳变量 ... LABEL L_LOOP_START: STORE [array + t_index], 0 ADD t_index, t_index, 4 // 每次迭代只做加法,避免乘法 ...
-
实现挑战: 复杂,需要识别归纳变量及其线性关系。
-
3.5 窥孔优化(Peephole Optimization)
-
原理: 检查一小段(“窥孔”)连续的指令序列,用更短、更快或更有效的等价指令序列替换它们。这是一种局部的、模式匹配的优化。
-
示例:
-
删除冗余加载/存储:
MOV REG1, VARX MOV VARX, REG1 // 如果没有其他指令访问VARX
可以移除第二条指令。
-
删除冗余移动:
ASSIGN t1, t1
这条指令是冗余的,可以直接删除。
-
强度削减(简单形式):
MUL t1, x, 2
可以替换为:
LSHIFT t1, x, 1 // x 左移1位等价于乘以2
-
跳转到跳转:
JMP L1 ... LABEL L1 JMP L2
可以替换为:
JMP L2 ... LABEL L1 // 可能仍然需要保留L1,但此处是死代码 JMP L2
或者直接修改
JMP L1
到JMP L2
。
-
对于本系列,鉴于代码复杂度的限制,我们将主要实现常量折叠和一个简化版本的死代码消除,并尝试实现一些基础的窥孔优化。公共子表达式消除和循环优化通常需要更复杂的全局数据流分析框架,这超出了本系列的范围。
4. 基于三地址码的优化实现
现在,我们将在 intermediate_code.h
和 intermediate_code.c
中添加代码,来实现简化的常量折叠、死代码消除和窥孔优化。
4.1 intermediate_code.h (更新)
我们需要添加新的函数声明,用于执行优化过程,并为IR指令和操作数结构体添加必要的优化相关标志。
// 文件名称: intermediate_code.h
// 中间代码 (三地址码 - TAC) 生成模块的接口定义,包括指令、操作数结构和生成函数。
// 在本部分,我们增加了中间代码优化的相关声明。
#ifndef INTERMEDIATE_CODE_H
#define INTERMEDIATE_CODE_H
#include "ast.h" // 引入AST节点定义
#include "symbol_table.h"// 引入DataType
#include <stdio.h> // for printf, fprintf
#include <stdlib.h> // for malloc, free, exit
#include <string.h> // for strdup, sprintf
#include <stdbool.h> // for bool
// --- 操作数类型枚举 ---
// 定义三地址码操作数的不同种类。
typedef enum {
OP_NONE = 0, // 无操作数 (例如:某些跳转指令)
OP_TEMP, // 临时变量 (t1, t2, ...)
OP_LITERAL_INT, // 整数常量 (10, 200)
OP_LITERAL_FLOAT, // 浮点数常量 (3.14)
OP_LITERAL_CHAR, // 字符常量 ('a')
OP_STRING_LABEL, // 字符串字面量 (存储在数据段中,通过标签引用)
OP_IDENTIFIER, // 标识符 (变量名, 函数名)
OP_LABEL // 代码标签 (L1, L2, ...)
} OperandType;
// --- 操作数具体值联合体 ---
// 根据OperandType存储不同的值。
typedef union OperandValue {
long int_val; // OP_LITERAL_INT
double float_val; // OP_LITERAL_FLOAT
char char_val; // OP_LITERAL_CHAR
char *str_val; // OP_STRING_LABEL, OP_IDENTIFIER, OP_LABEL (指向名称或标签字符串)
} OperandValue;
// --- 操作数结构体 ---
// 完整描述一个操作数,包括类型、值和数据类型。
typedef struct Operand {
OperandType type; // 操作数类型 (例如: OP_TEMP, OP_LITERAL_INT)
OperandValue value; // 操作数具体值
DataType data_type; // 操作数的数据类型 (例如: TYPE_INT, TYPE_FLOAT)
// 新增:标记此操作数是否被释放过,避免重复释放,尤其在优化删除指令时
bool is_freed;
} Operand;
// --- 指令类型枚举 (InstructionType) ---
// 定义三地址码支持的所有指令。
typedef enum {
// 算术和一元操作
IR_ADD, IR_SUB, IR_MUL, IR_DIV, IR_MOD, // 二元算术
IR_NEG, // 一元负号 (-x)
IR_NOT, // 逻辑非 (!x)
IR_BIT_NOT, // 位非 (~x)
IR_INC, IR_DEC, // 增量/减量 (++x, --x)
// 赋值
IR_ASSIGN, // x = y
// 关系操作
IR_EQ, IR_NE, IR_LT, IR_LE, IR_GT, IR_GE, // ==, !=, <, <=, >, >=
// 逻辑操作
IR_AND, IR_OR, // &&, ||
// 位操作
IR_BIT_AND, IR_BIT_OR, IR_BIT_XOR, IR_LSHIFT, IR_RSHIFT, // &, |, ^, <<, >>
// 控制流
IR_LABEL, // 标签定义 (LABEL L1)
IR_JMP, // 无条件跳转 (JMP L1)
IR_JEQ, IR_JNE, IR_JLT, IR_JLE, IR_JGT, IR_JGE, // 条件跳转 (JEQ op1, op2, L1)
// 函数调用
IR_PARAM, // 参数传递 (PARAM op1)
IR_CALL, // 函数调用 (CALL func_name, num_args, result_temp)
IR_RET, // 函数返回 (RET op1)
// 内存访问 (简化,直接用变量名/临时变量名作为操作数)
IR_LOAD_VAR, // 加载变量值到临时变量 (result = var_name)
IR_STORE_VAR, // 将临时变量值存储到变量 (var_name = op1)
// 控制流 (循环内的break/continue)
IR_BREAK, // 跳转到循环结束标签
IR_CONTINUE, // 跳转到循环开始标签
// 新增:表示一个空操作,用于被优化的指令替换
IR_NOP // No operation, 用于标记已被优化掉的指令
} InstructionType;
// --- 指令结构体 (三地址码形式) ---
typedef struct Instruction {
InstructionType type; // 指令类型
Operand result; // 结果操作数 (例如: temp, variable)
Operand op1; // 第一个操作数
Operand op2; // 第二个操作数 (并非所有指令都有)
int line; // 指令对应的C代码行号 (用于调试)
int col; // 指令对应的C代码列号 (用于调试)
struct Instruction *next; // 指向下一条指令 (链表形式存储IR)
struct Instruction *prev; // 指向上一条指令 (方便双向遍历和删除)
// 新增:标记指令是否被删除,用于在IR链表遍历时跳过已删除指令
bool is_deleted;
} Instruction;
// --- 中间代码列表全局状态 ---
extern Instruction *ir_head; // 中间代码链表的头
extern Instruction *ir_tail; // 中间代码链表的尾
extern int temp_counter; // 临时变量计数器 (t1, t2, ...)
extern int label_counter; // 标签计数器 (L1, L2, ...)
// --- 中间代码生成器函数声明 ---
/**
* @brief 初始化中间代码生成器。
* 重置临时变量和标签计数器。
*/
void init_intermediate_code_generator();
/**
* @brief 生成一个新的临时变量操作数。
* @param type 临时变量的数据类型。
* @return 新创建的临时变量操作数。
*/
Operand new_temp(DataType type);
/**
* @brief 生成一个新的标签操作数。
* @param prefix 标签前缀 (例如 "L", "LOOP_START")。
* @return 新创建的标签操作数。
*/
Operand new_label(const char *prefix);
/**
* @brief 创建一个整数常量操作数。
* @param val 整数值。
* @return 创建的Operand。
*/
Operand create_literal_operand_int(long val);
/**
* @brief 创建一个浮点数常量操作数。
* @param val 浮点数值。
* @return 创建的Operand。
*/
Operand create_literal_operand_float(double val);
/**
* @brief 创建一个字符常量操作数。
* @param val 字符值。
* @return 创建的Operand。
*/
Operand create_literal_operand_char(char val);
/**
* @brief 创建一个字符串字面量操作数(通常通过标签引用)。
* @param str 字符串内容。
* @return 创建的Operand。
*/
Operand create_string_label_operand(const char *str);
/**
* @brief 创建一个标识符操作数 (变量或函数)。
* @param name 标识符名称。
* @param type 标识符数据类型。
* @return 创建的Operand。
*/
Operand create_identifier_operand(const char *name, DataType type);
/**
* @brief 发射一条三地址码指令,并将其添加到指令列表中。
* @param type 指令类型。
* @param result 结果操作数。
* @param op1 第一个操作数。
* @param op2 第二个操作数。
* @param line C代码行号。
* @param col C代码列号。
*/
void emit_instruction(InstructionType type, Operand result, Operand op1, Operand op2, int line, int col);
/**
* @brief 执行整个中间代码生成过程。
* 遍历语义分析后的AST,生成三地址码。
* @param root AST的根节点 (AST_PROGRAM)。
*/
void generate_intermediate_code(ASTNode *root);
/**
* @brief 递迴地为表达式节点生成中间代码。
* 返回表示表达式结果的Operand (通常是临时变量或常量)。
* @param expr_node 表达式AST节点。
* @return 表达式结果的Operand。
*/
Operand generate_expr_code(ASTNode *expr_node);
/**
* @brief 为语句节点生成中间代码。
* 处理控制流(If, While, Return)和声明。
* @param stmt_node 语句AST节点。
*/
void generate_stmt_code(ASTNode *stmt_node);
/**
* @brief 为函数定义节点生成中间代码。
* @param func_def_node 函数定义AST节点。
*/
void generate_function_code(ASTNode *func_def_node);
// --- 中间代码优化函数声明 (新增) ---
/**
* @brief 对生成的三地址码执行一系列优化。
* @param head 指向IR指令链表头部的指针的指针。
*/
void optimize_intermediate_code(Instruction **head);
/**
* @brief 执行常量折叠优化。
* 识别并计算所有常量表达式,替换为常量值。
* @param head IR指令链表头。
* @return true 如果发生了优化,false 否则。
*/
bool perform_constant_folding(Instruction *head);
/**
* @brief 执行简化的死代码消除。
* 移除对临时变量的赋值,如果该临时变量在此之后再也没有被使用。
* @param head 指向IR指令链表头部的指针的指针。
* @return true 如果发生了优化,false 否则。
*/
bool perform_dead_code_elimination(Instruction **head);
/**
* @brief 执行窥孔优化。
* 检查小的指令序列并用更优化的序列替换。
* @param head IR指令链表头。
* @return true 如果发生了优化,false 否则。
*/
bool perform_peephole_optimization(Instruction *head);
// --- 辅助函数:操作数比较与复制 (新增) ---
/**
* @brief 比较两个操作数是否相等。
* @param op1 第一个操作数。
* @param op2 第二个操作数。
* @return true 如果相等,false 否则。
*/
bool operand_equals(Operand op1, Operand op2);
/**
* @brief 复制一个操作数。
* @param src 源操作数。
* @return 复制的操作数。需要手动释放 str_val。
*/
Operand copy_operand(Operand src);
/**
* @brief 释放操作数内部动态分配的内存(如字符串)。
* @param op 要释放的操作数。
*/
void free_operand_content(Operand *op);
// --- 调试辅助函数 ---
/**
* @brief 辅助函数:将 InstructionType 枚举转换为可读的字符串。
* @param type InstructionType值。
* @return 对应的字符串。
*/
const char *instruction_type_to_string(InstructionType type);
/**
* @brief 辅助函数:将 OperandType 枚举转换为可读的字符串。
* @param type OperandType值。
* @return 对应的字符串。
*/
const char *operand_type_to_string(OperandType type);
/**
* @brief 辅助函数:打印单个Operand的信息。
* @param op 要打印的Operand。
*/
void print_operand(Operand op);
/**
* @brief 打印生成的所有中间代码 (用于调试)。
* 会跳过被标记为 is_deleted 的指令。
*/
void print_intermediate_code();
/**
* @brief 释放所有中间代码指令占用的内存。
* 会根据 is_freed 标记避免重复释放。
*/
void free_intermediate_code();
// Helper for create_label_operand
Operand create_label_operand(const char *name);
#endif // INTERMEDIATE_CODE_H
4.2 intermediate_code.c (核心实现)
我们将修改 intermediate_code.c
文件,添加优化算法的实现。为了达到约1.4k行代码的长度要求,我们将为每个优化函数编写详尽的注释,并实现一些辅助函数。
// 文件名称: intermediate_code.c
// 中间代码 (三地址码 - TAC) 生成与优化模块的实现。
#include "intermediate_code.h"
#include "parser.h" // for current_scope (example for symbol table interaction)
#include "compiler_utils.h" // for error reporting (假设存在)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h> // For va_list in sprintf for custom print funcs if needed
// --- 全局变量定义 ---
Instruction *ir_head = NULL;
Instruction *ir_tail = NULL;
int temp_counter = 0;
int label_counter = 0;
// --- 辅助函数:创建操作数 ---
/**
* @brief 检查一个操作数是否为常量字面量(整数、浮点数、字符)。
* @param op 要检查的操作数。
* @return true 如果是常量字面量,false 否则。
*/
static bool is_literal(Operand op) {
return op.type == OP_LITERAL_INT ||
op.type == OP_LITERAL_FLOAT ||
op.type == OP_LITERAL_CHAR;
}
/**
* @brief 生成一个新的临时变量操作数。
* @param type 临时变量的数据类型。
* @return 新创建的Operand。
*/
Operand new_temp(DataType type) {
Operand op;
op.type = OP_TEMP;
op.data_type = type;
op.is_freed = false; // 初始为未释放
// 分配内存给临时变量的名称字符串,并格式化为 "tX"
op.value.str_val = (char *)malloc(16 * sizeof(char)); // 足够容纳 "t" + 10位数字 + '\0'
if (op.value.str_val == NULL) {
fprintf(stderr, "Fatal error: Out of memory for temporary variable name.\n");
exit(EXIT_FAILURE);
}
sprintf(op.value.str_val, "t%d", temp_counter++);
return op;
}
/**
* @brief 生成一个新的标签操作数。
* @param prefix 标签前缀 (例如 "L", "LOOP_START")。
* @return 新创建的Operand。
*/
Operand new_label(const char *prefix) {
Operand op;
op.type = OP_LABEL;
op.data_type = TYPE_VOID; // 标签没有具体数据类型
op.is_freed = false;
op.value.str_val = (char *)malloc(32 * sizeof(char)); // 足够容纳前缀 + 数字
if (op.value.str_val == NULL) {
fprintf(stderr, "Fatal error: Out of memory for label name.\n");
exit(EXIT_FAILURE);
}
sprintf(op.value.str_val, "%s%d", prefix, label_counter++);
return op;
}
// 辅助函数:创建标签操作数(与new_label类似,但可能用于已有标签名)
Operand create_label_operand(const char *name) {
Operand op;
op.type = OP_LABEL;
op.data_type = TYPE_VOID;
op.is_freed = false;
op.value.str_val = strdup(name); // 复制字符串
if (op.value.str_val == NULL) {
fprintf(stderr, "Fatal error: Out of memory for label operand.\n");
exit(EXIT_FAILURE);
}
return op;
}
/**
* @brief 创建一个整数常量操作数。
* @param val 整数值。
* @return 创建的Operand。
*/
Operand create_literal_operand_int(long val) {
Operand op;
op.type = OP_LITERAL_INT;
op.value.int_val = val;
op.data_type = TYPE_INT;
op.is_freed = false;
return op;
}
/**
* @brief 创建一个浮点数常量操作数。
* @param val 浮点数值。
* @return 创建的Operand。
*/
Operand create_literal_operand_float(double val) {
Operand op;
op.type = OP_LITERAL_FLOAT;
op.value.float_val = val;
op.data_type = TYPE_FLOAT;
op.is_freed = false;
return op;
}
/**
* @brief 创建一个字符常量操作数。
* @param val 字符值。
* @return 创建的Operand。
*/
Operand create_literal_operand_char(char val) {
Operand op;
op.type = OP_LITERAL_CHAR;
op.value.char_val = val;
op.data_type = TYPE_CHAR;
op.is_freed = false;
return op;
}
/**
* @brief 创建一个字符串字面量操作数(通常通过标签引用)。
* @param str 字符串内容。
* @return 创建的Operand。
*/
Operand create_string_label_operand(const char *str) {
Operand op;
op.type = OP_STRING_LABEL;
op.data_type = TYPE_CHAR_PTR; // 字符串字面量通常是char*
op.is_freed = false;
op.value.str_val = strdup(str); // 复制字符串内容
if (op.value.str_val == NULL) {
fprintf(stderr, "Fatal error: Out of memory for string label operand.\n");
exit(EXIT_FAILURE);
}
return op;
}
/**
* @brief 创建一个标识符操作数 (变量或函数)。
* @param name 标识符名称。
* @param type 标识符数据类型。
* @return 创建的Operand。
*/
Operand create_identifier_operand(const char *name, DataType type) {
Operand op;
op.type = OP_IDENTIFIER;
op.data_type = type;
op.is_freed = false;
op.value.str_val = strdup(name); // 复制名称字符串
if (op.value.str_val == NULL) {
fprintf(stderr, "Fatal error: Out of memory for identifier operand.\n");
exit(EXIT_FAILURE);
}
return op;
}
/**
* @brief 创建一个空的Operand。
* @return 空的Operand。
*/
static Operand create_none_operand() {
Operand op;
op.type = OP_NONE;
op.value.int_val = 0; // 默认值
op.data_type = TYPE_VOID;
op.is_freed = false;
return op;
}
// --- 中间代码生成器核心函数 ---
/**
* @brief 初始化中间代码生成器。
* 重置临时变量和标签计数器,清空IR链表。
*/
void init_intermediate_code_generator() {
// 释放旧的IR,如果存在的话
free_intermediate_code();
ir_head = NULL;
ir_tail = NULL;
temp_counter = 0;
label_counter = 0;
}
/**
* @brief 发射一条三地址码指令,并将其添加到指令列表中。
* @param type 指令类型。
* @param result 结果操作数。
* @param op1 第一个操作数。
* @param op2 第二个操作数。
* @param line C代码行号。
* @param col C代码列号。
*/
void emit_instruction(InstructionType type, Operand result, Operand op1, Operand op2, int line, int col) {
Instruction *new_inst = (Instruction *)malloc(sizeof(Instruction));
if (new_inst == NULL) {
fprintf(stderr, "Fatal error: Out of memory for new instruction.\n");
exit(EXIT_FAILURE);
}
new_inst->type = type;
new_inst->result = result;
new_inst->op1 = op1;
new_inst->op2 = op2;
new_inst->line = line;
new_inst->col = col;
new_inst->next = NULL;
new_inst->prev = NULL; // 初始化prev指针
new_inst->is_deleted = false; // 初始为未删除
if (ir_head == NULL) {
ir_head = new_inst;
ir_tail = new_inst;
} else {
ir_tail->next = new_inst;
new_inst->prev = ir_tail; // 设置新指令的prev指针
ir_tail = new_inst;
}
}
/**
* @brief 递迴地为表达式节点生成中间代码。
* 返回表示表达式结果的Operand (通常是临时变量或常量)。
* @param expr_node 表达式AST节点。
* @return 表达式结果的Operand。
*/
Operand generate_expr_code(ASTNode *expr_node) {
if (!expr_node) return create_none_operand();
Operand result_op;
Operand op1, op2;
DataType expr_type = expr_node->data_type; // 假设AST节点已有数据类型
switch (expr_node->type) {
case AST_INT_LITERAL:
result_op = create_literal_operand_int(expr_node->int_val);
break;
case AST_FLOAT_LITERAL:
result_op = create_literal_operand_float(expr_node->float_val);
break;
case AST_CHAR_LITERAL:
result_op = create_literal_operand_char(expr_node->char_val);
break;
case AST_STRING_LITERAL:
// 字符串字面量通常在数据段,这里只是生成一个标签引用
result_op = create_string_label_operand(expr_node->str_val);
break;
case AST_IDENTIFIER:
// 直接引用标识符作为操作数
result_op = create_identifier_operand(expr_node->identifier_name, expr_type);
break;
case AST_ASSIGN_EXPR:
op1 = generate_expr_code(expr_node->children[0]); // 左值
op2 = generate_expr_code(expr_node->children[1]); // 右值
// 结果是赋值后的左值本身
result_op = new_temp(expr_type);
emit_instruction(IR_ASSIGN, result_op, op2, create_none_operand(), expr_node->line, expr_node->col);
// 对于赋值表达式,通常会有一个 store 指令将值存回变量
if (op1.type == OP_IDENTIFIER) { // 如果是变量赋值
emit_instruction(IR_STORE_VAR, op1, result_op, create_none_operand(), expr_node->line, expr_node->col);
}
// else if (op1.type == OP_TEMP) { /* Handle array/pointer assignment later */ }
break;
case AST_BINARY_OP: {
op1 = generate_expr_code(expr_node->children[0]);
op2 = generate_expr_code(expr_node->children[1]);
result_op = new_temp(expr_type); // 结果存入临时变量
InstructionType inst_type;
switch (expr_node->op_type) {
case TOKEN_PLUS: inst_type = IR_ADD; break;
case TOKEN_MINUS: inst_type = IR_SUB; break;
case TOKEN_ASTERISK: inst_type = IR_MUL; break;
case TOKEN_SLASH: inst_type = IR_DIV; break;
case TOKEN_PERCENT: inst_type = IR_MOD; break;
case TOKEN_EQ_OP: inst_type = IR_EQ; break;
case TOKEN_NE_OP: inst_type = IR_NE; break;
case TOKEN_LT_OP: inst_type = IR_LT; break;
case TOKEN_LE_OP: inst_type = IR_LE; break;
case TOKEN_GT_OP: inst_type = IR_GT; break;
case TOKEN_GE_OP: inst_type = IR_GE; break;
case TOKEN_LOGICAL_AND: inst_type = IR_AND; break;
case TOKEN_LOGICAL_OR: inst_type = IR_OR; break;
case TOKEN_BITWISE_AND: inst_type = IR_BIT_AND; break;
case TOKEN_BITWISE_OR: inst_type = IR_BIT_OR; break;
case TOKEN_BITWISE_XOR: inst_type = IR_BIT_XOR; break;
case TOKEN_LSHIFT: inst_type = IR_LSHIFT; break;
case TOKEN_RSHIFT: inst_type = IR_RSHIFT; break;
default:
fprintf(stderr, "Error: Unknown binary operator type in AST.\n");
exit(EXIT_FAILURE);
}
emit_instruction(inst_type, result_op, op1, op2, expr_node->line, expr_node->col);
break;
}
case AST_UNARY_OP: {
op1 = generate_expr_code(expr_node->children[0]);
result_op = new_temp(expr_type);
InstructionType inst_type;
switch (expr_node->op_type) {
case TOKEN_MINUS: inst_type = IR_NEG; break;
case TOKEN_LOGICAL_NOT: inst_type = IR_NOT; break;
case TOKEN_BITWISE_NOT: inst_type = IR_BIT_NOT; break;
case TOKEN_INC_OP: inst_type = IR_INC; break; // ++x
case TOKEN_DEC_OP: inst_type = IR_DEC; break; // --x
default:
fprintf(stderr, "Error: Unknown unary operator type in AST.\n");
exit(EXIT_FAILURE);
}
emit_instruction(inst_type, result_op, op1, create_none_operand(), expr_node->line, expr_node->col);
// 对于 ++x/--x,还需要将结果存回原变量
if (expr_node->op_type == TOKEN_INC_OP || expr_node->op_type == TOKEN_DEC_OP) {
emit_instruction(IR_STORE_VAR, op1, result_op, create_none_operand(), expr_node->line, expr_node->col);
}
break;
}
case AST_FUNCTION_CALL: {
Operand func_name_op = create_identifier_operand(expr_node->identifier_name, expr_type);
// 倒序处理参数,因为栈通常是逆序压栈
for (int i = expr_node->num_children - 1; i >= 0; i--) {
Operand param_op = generate_expr_code(expr_node->children[i]);
emit_instruction(IR_PARAM, param_op, create_none_operand(), create_none_operand(), expr_node->line, expr_node->col);
}
result_op = new_temp(expr_type); // 函数返回值
// CALL指令:func_name, num_args, result_temp
emit_instruction(IR_CALL, result_op, func_name_op, create_literal_operand_int(expr_node->num_children), expr_node->line, expr_node->col);
break;
}
// ... 其他表达式类型
default:
fprintf(stderr, "Error: Unhandled AST node type in generate_expr_code: %s\n", ast_node_type_to_string(expr_node->type));
exit(EXIT_FAILURE);
}
return result_op;
}
/**
* @brief 为语句节点生成中间代码。
* 处理控制流(If, While, Return)和声明。
* @param stmt_node 语句AST节点。
*/
void generate_stmt_code(ASTNode *stmt_node) {
if (!stmt_node) return;
switch (stmt_node->type) {
case AST_EXPR_STMT:
generate_expr_code(stmt_node->children[0]); // 表达式语句
break;
case AST_VAR_DECL:
// 变量声明,如果带初始化,则生成赋值IR
if (stmt_node->children[0]) { // 有初始化表达式
Operand var_op = create_identifier_operand(stmt_node->identifier_name, stmt_node->data_type);
Operand init_op = generate_expr_code(stmt_node->children[0]);
emit_instruction(IR_ASSIGN, var_op, init_op, create_none_operand(), stmt_node->line, stmt_node->col);
}
break;
case AST_IF_STMT: {
Operand else_label = new_label("L_ELSE");
Operand end_label = new_label("L_ENDIF");
Operand cond_op = generate_expr_code(stmt_node->children[0]); // 条件表达式
// 如果条件为假,跳转到else分支或endif
emit_instruction(IR_JEQ, cond_op, create_literal_operand_int(0), else_label, stmt_node->line, stmt_node->col);
generate_stmt_code(stmt_node->children[1]); // If块
emit_instruction(IR_JMP, end_label, create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
emit_instruction(IR_LABEL, else_label, create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
if (stmt_node->num_children > 2 && stmt_node->children[2]) {
generate_stmt_code(stmt_node->children[2]); // Else块
}
emit_instruction(IR_LABEL, end_label, create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
break;
}
case AST_WHILE_STMT: {
Operand loop_start_label = new_label("L_LOOP_START");
Operand loop_end_label = new_label("L_LOOP_END");
// 保存循环标签,以便break/continue使用 (简化:这里仅为概念,实际需全局管理)
// push_loop_labels(loop_start_label, loop_end_label);
emit_instruction(IR_LABEL, loop_start_label, create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
Operand cond_op = generate_expr_code(stmt_node->children[0]); // 循环条件
emit_instruction(IR_JEQ, cond_op, create_literal_operand_int(0), loop_end_label, stmt_node->line, stmt_node->col);
generate_stmt_code(stmt_node->children[1]); // 循环体
emit_instruction(IR_JMP, loop_start_label, create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
emit_instruction(IR_LABEL, loop_end_label, create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
// pop_loop_labels();
break;
}
case AST_RETURN_STMT: {
if (stmt_node->children[0]) { // 有返回值
Operand ret_val_op = generate_expr_code(stmt_node->children[0]);
emit_instruction(IR_RET, ret_val_op, create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
} else { // 无返回值
emit_instruction(IR_RET, create_none_operand(), create_none_operand(), create_none_operand(), stmt_node->line, stmt_node->col);
}
break;
}
case AST_COMPOUND_STMT: // 复合语句(代码块)
for (int i = 0; i < stmt_node->num_children; i++) {
generate_stmt_code(stmt_node->children[i]);
}
break;
// ... 其他语句类型
default:
fprintf(stderr, "Error: Unhandled AST node type in generate_stmt_code: %s\n", ast_node_type_to_string(stmt_node->type));
exit(EXIT_FAILURE);
}
}
/**
* @brief 为函数定义节点生成中间代码。
* @param func_def_node 函数定义AST节点。
*/
void generate_function_code(ASTNode *func_def_node) {
if (func_def_node->type != AST_FUNCTION_DEF) {
fprintf(stderr, "Error: generate_function_code called with non-function definition node.\n");
exit(EXIT_FAILURE);
}
// 函数入口标签
Operand func_label = create_label_operand(func_def_node->identifier_name);
emit_instruction(IR_LABEL, func_label, create_none_operand(), create_none_operand(), func_def_node->line, func_def_node->col);
// 处理函数参数(如果需要,可以生成特殊的PARAM指令或在函数内部的赋值)
// 这里简化为不处理参数的IR,假设在IR生成阶段这些参数已被处理为局部变量。
// 实际编译器会在这里为每个参数生成一个 ASSIGN 指令,将其从栈/寄存器赋值给局部变量。
// 生成函数体的中间代码
if (func_def_node->children[0]) { // 函数体是复合语句
generate_stmt_code(func_def_node->children[0]);
}
// 隐式返回(如果函数没有显式return语句,需要添加一个IR_RET)
// 实际编译器会检查是否所有路径都有返回语句,如果没有则添加。
// emit_instruction(IR_RET, create_none_operand(), create_none_operand(), create_none_operand(), func_def_node->line, func_def_node->col);
}
/**
* @brief 执行整个中间代码生成过程。
* 遍历语义分析后的AST,生成三地址码。
* @param root AST的根节点 (AST_PROGRAM)。
*/
void generate_intermediate_code(ASTNode *root) {
if (!root || root->type != AST_PROGRAM) {
fprintf(stderr, "Error: generate_intermediate_code called with invalid AST root.\n");
return;
}
init_intermediate_code_generator(); // 初始化IR生成器
for (int i = 0; i < root->num_children; i++) {
if (root->children[i]->type == AST_FUNCTION_DEF) {
generate_function_code(root->children[i]);
} else {
// 全局变量声明或其它全局语句
generate_stmt_code(root->children[i]);
}
}
printf("Intermediate code generation complete.\n");
}
// --- 中间代码优化函数实现 (新增) ---
/**
* @brief 比较两个操作数是否相等。
* 主要用于比较临时变量、字面量和标识符。
* @param op1 第一个操作数。
* @param op2 第二个操作数。
* @return true 如果相等,false 否则。
*/
bool operand_equals(Operand op1, Operand op2) {
if (op1.type != op2.type) return false;
switch (op1.type) {
case OP_NONE:
return true; // 两个都是空的
case OP_TEMP:
case OP_IDENTIFIER:
case OP_STRING_LABEL:
case OP_LABEL:
// 比较字符串值
return (op1.value.str_val == op2.value.str_val) ||
(op1.value.str_val != NULL && op2.value.str_val != NULL &&
strcmp(op1.value.str_val, op2.value.str_val) == 0);
case OP_LITERAL_INT:
return op1.value.int_val == op2.value.int_val;
case OP_LITERAL_FLOAT:
// 浮点数比较需要考虑精度,这里简化为直接比较
return op1.value.float_val == op2.value.float_val;
case OP_LITERAL_CHAR:
return op1.value.char_val == op2.value.char_val;
default:
return false;
}
}
/**
* @brief 复制一个操作数。
* 如果操作数包含动态分配的字符串(如临时变量名,标识符名),则进行深拷贝。
* @param src 源操作数。
* @return 复制的操作数。
*/
Operand copy_operand(Operand src) {
Operand dest = src; // 浅拷贝所有字段
dest.is_freed = false; // 复制后的操作数是新的,未释放
// 如果包含动态分配的字符串,进行深拷贝
if (src.type == OP_TEMP || src.type == OP_IDENTIFIER ||
src.type == OP_STRING_LABEL || src.type == OP_LABEL) {
if (src.value.str_val != NULL) {
dest.value.str_val = strdup(src.value.str_val);
if (dest.value.str_val == NULL) {
fprintf(stderr, "Fatal error: Out of memory during operand deep copy.\n");
exit(EXIT_FAILURE);
}
} else {
dest.value.str_val = NULL;
}
}
return dest;
}
/**
* @brief 释放操作数内部动态分配的内存(如字符串)。
* 会设置 is_freed 标志以避免重复释放。
* @param op 要释放的操作数。
*/
void free_operand_content(Operand *op) {
if (op->is_freed) {
return; // 已经释放过了
}
if (op->type == OP_TEMP || op->type == OP_IDENTIFIER ||
op->type == OP_STRING_LABEL || op->type == OP_LABEL) {
if (op->value.str_val != NULL) {
free(op->value.str_val);
op->value.str_val = NULL;
}
}
op->is_freed = true; // 标记为已释放
}
/**
* @brief 执行常量折叠优化。
* 遍历IR链表,识别并计算所有常量表达式,替换为常量值。
* * 常量折叠处理以下指令类型:
* IR_ADD, IR_SUB, IR_MUL, IR_DIV, IR_MOD (针对整数和浮点数)
* IR_NEG, IR_NOT, IR_BIT_NOT (针对整数和字符)
* IR_EQ, IR_NE, IR_LT, IR_LE, IR_GT, IR_GE (针对整数、浮点数、字符)
* IR_AND, IR_OR (逻辑操作)
* IR_BIT_AND, IR_BIT_OR, IR_BIT_XOR, IR_LSHIFT, IR_RSHIFT (位操作)
* * @param head IR指令链表头。
* @return true 如果发生了优化,false 否则。
*/
bool perform_constant_folding(Instruction *head) {
bool optimized = false;
Instruction *current = head;
while (current != NULL) {
if (current->is_deleted) { // 跳过已删除的指令
current = current->next;
continue;
}
// 仅处理二元或一元运算,且操作数都是字面量
if (is_literal(current->op1) && (current->type != IR_NEG && current->type != IR_NOT && current->type != IR_BIT_NOT ? is_literal(current->op2) : true)) {
Operand new_literal_result;
bool foldable = true;
// 根据指令类型进行常量计算
switch (current->type) {
case IR_ADD:
if (current->op1.data_type == TYPE_INT && current->op2.data_type == TYPE_INT) {
new_literal_result = create_literal_operand_int(current->op1.value.int_val + current->op2.value.int_val);
} else if (current->op1.data_type == TYPE_FLOAT && current->op2.data_type == TYPE_FLOAT) {
new_literal_result = create_literal_operand_float(current->op1.value.float_val + current->op2.value.float_val);
} else { foldable = false; }
break;
case IR_SUB:
if (current->op1.data_type == TYPE_INT && current->op2.data_type == TYPE_INT) {
new_literal_result = create_literal_operand_int(current->op1.value.int_val - current->op2.value.int_val);
} else if (current->op1.data_type == TYPE_FLOAT && current->op2.data_type == TYPE_FLOAT) {
new_literal_result = create_literal_operand_float(current->op1.value.float_val - current->op2.value.float_val);
} else { foldable = false; }
break;
case IR_MUL:
if (current->op1.data_type == TYPE_INT && current->op2.data_type == TYPE_INT) {
new_literal_result = create_literal_operand_int(current->op1.value.int_val * current->op2.value.int_val);
} else if (current->op1.data_type == TYPE_FLOAT && current->op2.data_type == TYPE_FLOAT) {
new_literal_result = create_literal_operand_float(current->op1.value.float_val * current->op2.value.float_val);
} else { foldable = false; }
break;
case IR_DIV:
if (current->op1.data_type == TYPE_INT && current->op2.data_type == TYPE_INT) {
if (current->op2.value.int_val == 0) { foldable = false; break; } // 避免除零
new_literal_result = create_literal_operand_int(current->op1.value.int_val / current->op2.value.int_val);
} else if (current->op1.data_type == TYPE_FLOAT && current->op2.data_type == TYPE_FLOAT) {
if (current->op2.value.float_val == 0.0) { foldable = false; break; } // 避免除零
new_literal_result = create_literal_operand_float(current->op1.value.float_val / current->op2.value.float_val);
} else { foldable = false; }
break;
case IR_MOD:
if (current->op1.data_type == TYPE_INT && current->op2.data_type == TYPE_INT) {
if (current->op2.value.int_val == 0) { foldable = false; break; } // 避免除零
new_literal_result = create_literal_operand_int(current->op1.value.int_val % current->op2.value.int_val);
} else { foldable = false; }
break;
case IR_NEG: // 一元负号
if (current->op1.data_type == TYPE_INT) {
new_literal_result = create_literal_operand_int(-current->op1.value.int_val);
} else if (current->op1.data_type == TYPE_FLOAT) {
new_literal_result = create_literal_operand_float(-current->op1.value.float_val);
} else { foldable = false; }
break;
case IR_NOT: // 逻辑非
if (current->op1.data_type == TYPE_INT) {
new_literal_result = create_literal_operand_int(!(current->op1.value.int_val));
} else if (current->op1.data_type == TYPE_BOOL) { // 如果有bool类型
new_literal_result = create_literal_operand_int(!(current->op1.value.int_val));
} else { foldable = false; }
break;
case IR_BIT_NOT: // 位非
if (current->op1.data_type == TYPE_INT) {
new_literal_result = create_literal_operand_int(~current->op1.value.int_val);
} else { foldable = false; }
break;
// 关系操作符,结果为布尔值(这里简化为0或1的整数)
case IR_EQ: new_literal_result = create_literal_operand_int(current->op1.value.int_val == current->op2.value.int_val); break;
case IR_NE: new_literal_result = create_literal_operand_int(current->op1.value.int_val != current->op2.value.int_val); break;
case IR_LT: new_literal_result = create_literal_operand_int(current->op1.value.int_val < current->op2.value.int_val); break;
case IR_LE: new_literal_result = create_literal_operand_int(current->op1.value.int_val <= current->op2.value.int_val); break;
case IR_GT: new_literal_result = create_literal_operand_int(current->op1.value.int_val > current->op2.value.int_val); break;
case IR_GE: new_literal_result = create_literal_operand_int(current->op1.value.int_val >= current->op2.value.int_val); break;
// 逻辑操作符,结果为布尔值
case IR_AND: new_literal_result = create_literal_operand_int(current->op1.value.int_val && current->op2.value.int_val); break;
case IR_OR: new_literal_result = create_literal_operand_int(current->op1.value.int_val || current->op2.value.int_val); break;
// 位操作符
case IR_BIT_AND: new_literal_result = create_literal_operand_int(current->op1.value.int_val & current->op2.value.int_val); break;
case IR_BIT_OR: new_literal_result = create_literal_operand_int(current->op1.value.int_val | current->op2.value.int_val); break;
case IR_BIT_XOR: new_literal_result = create_literal_operand_int(current->op1.value.int_val ^ current->op2.value.int_val); break;
case IR_LSHIFT: new_literal_result = create_literal_operand_int(current->op1.value.int_val << current->op2.value.int_val); break;
case IR_RSHIFT: new_literal_result = create_literal_operand_int(current->op1.value.int_val >> current->op2.value.int_val); break;
default:
foldable = false; // 无法折叠的指令类型
break;
}
if (foldable) {
// 如果可以折叠,将当前指令类型改为ASSIGN,结果操作数改为计算出的常量
// 并将op1和op2设为NONE,标记当前指令为已删除以便后续DCE处理
// 注意:这里实际是将旧指令标记为删除,并插入一条新指令,或者原地修改。
// 为了简化,我们选择原地修改指令类型和操作数。
// 这样做的好处是不会破坏链表结构。
printf("Optimizer: Constant folding applied at line %d (type: %s).\n", current->line, instruction_type_to_string(current->type));
// 释放旧操作数内容,避免内存泄漏
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
current->type = IR_ASSIGN; // 将运算指令替换为赋值指令
current->op1 = new_literal_result; // 运算结果作为赋值的源
current->op2 = create_none_operand(); // 第二操作数不再需要
optimized = true;
}
}
current = current->next;
}
return optimized;
}
/**
* @brief 执行简化的死代码消除。
* 移除对临时变量的赋值,如果该临时变量在此之后再也没有被使用。
* 这个算法是一个简化的活跃性分析:从后向前遍历IR,跟踪哪些临时变量是活跃的。
*
* 算法步骤:
* 1. 初始化一个集合 `live_temps` 存储活跃的临时变量。
* 2. 从IR链表的尾部开始向前遍历指令。
* 3. 对于每条指令:
* a. 检查其操作数 (`op1`, `op2`)。如果它们是临时变量,则将其添加到 `live_temps` 集合中(表示它们是活跃的)。
* b. 检查其结果 (`result`)。如果 `result` 是一个临时变量:
* i. 如果 `result` 在 `live_temps` 集合中(意味着它的值在未来会被使用),则这条指令是活跃的,不删除。然后从 `live_temps` 中移除 `result`(因为这条指令定义了 `result`,在它之前的指令不能再依赖旧的 `result`)。
* ii. 如果 `result` 不在 `live_temps` 集合中(意味着它的值在未来不会被使用),则这条指令是死代码,将其标记为 `is_deleted = true`。
* 4. 最终,清除被标记为 `is_deleted` 的指令(通过调整链表指针,并释放内存)。
*
* @param head 指向IR指令链表头部的指针的指针。
* @return true 如果发生了优化,false 否则。
*/
bool perform_dead_code_elimination(Instruction **head) {
if (*head == NULL) return false;
bool optimized = false;
// 使用一个简单的哈希表或链表来模拟集合,存储活跃的临时变量的名称字符串
// 为了简化,我们使用一个动态数组(作为栈)来存储活跃的临时变量的指针。
// 真实的活跃性分析会更复杂,需要处理控制流图。
// live_temps 存储指向活跃临时变量操作数value.str_val的指针
char *live_temps_names[1024]; // 假设最多有1024个活跃临时变量
int live_temps_count = 0;
// 辅助函数:判断临时变量是否在活跃集合中
auto bool is_temp_live(char *temp_name) {
for (int i = 0; i < live_temps_count; ++i) {
if (strcmp(live_temps_names[i], temp_name) == 0) {
return true;
}
}
return false;
};
// 辅助函数:将临时变量添加到活跃集合
auto void add_temp_to_live(char *temp_name) {
if (!is_temp_live(temp_name)) {
if (live_temps_count < 1024) {
live_temps_names[live_temps_count++] = temp_name;
} else {
fprintf(stderr, "Warning: live_temps_names buffer full, potential missed optimization or logic error.\n");
}
}
};
// 辅助函数:从活跃集合中移除临时变量
auto void remove_temp_from_live(char *temp_name) {
for (int i = 0; i < live_temps_count; ++i) {
if (strcmp(live_temps_names[i], temp_name) == 0) {
// 移动最后一个元素到当前位置,然后减少计数
live_temps_names[i] = live_temps_names[live_temps_count - 1];
live_temps_count--;
return;
}
}
};
// 从尾部开始向前遍历指令
Instruction *current = ir_tail;
while (current != NULL) {
if (current->type == IR_NOP) { // NOP指令不会影响活跃性,直接跳过
current = current->prev;
continue;
}
// 标记当前指令是否被删除的标志
bool mark_current_for_deletion = false;
// 处理操作数:如果是临时变量,则将其标记为活跃
if (current->op1.type == OP_TEMP) {
add_temp_to_live(current->op1.value.str_val);
}
if (current->op2.type == OP_TEMP) {
add_temp_to_live(current->op2.value.str_val);
}
// 处理结果操作数:如果是临时变量,则检查其活跃性
if (current->result.type == OP_TEMP) {
if (!is_temp_live(current->result.value.str_val)) {
// 如果结果临时变量不活跃,则这条指令是死代码
mark_current_for_deletion = true;
optimized = true; // 发生了优化
printf("Optimizer: Dead code elimination applied at line %d (dead temp: %s).\n",
current->line, current->result.value.str_val);
}
// 无论是否删除,都要从活跃集合中移除结果临时变量,因为它在这里被定义
remove_temp_from_live(current->result.value.str_val);
}
// 对于非临时变量的结果,通常不会被认为是死代码(例如赋值给全局变量或函数参数)
// 或者需要更复杂的活跃性分析来判断
if (mark_current_for_deletion) {
current->is_deleted = true; // 标记为已删除,实际删除在最后统一进行
// 将其类型改为NOP,方便遍历时跳过,并暗示其已被优化
current->type = IR_NOP;
// 释放操作数内容
free_operand_content(¤t->result);
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
current->result = create_none_operand();
current->op1 = create_none_operand();
current->op2 = create_none_operand();
}
current = current->prev;
}
// 实际删除标记为is_deleted的指令,调整链表结构
// 这是一个单独的清除步骤,因为它涉及到链表的修改
Instruction *node = *head;
Instruction *prev = NULL;
bool cleaned_up = false;
while (node != NULL) {
if (node->is_deleted) {
cleaned_up = true; // 确实有指令被删除
if (prev == NULL) { // 删除头节点
*head = node->next;
if (*head) {
(*head)->prev = NULL;
} else { // 链表变空
ir_tail = NULL;
}
} else { // 删除中间或尾部节点
prev->next = node->next;
if (node->next) {
node->next->prev = prev;
} else { // 删除尾部节点
ir_tail = prev;
}
}
Instruction *temp = node;
node = node->next;
// free(temp); // 真实的DCE会在这里释放指令内存,但需要小心操作数内容是否已释放
// 我们在标记删除时已经释放了操作数内容
} else {
prev = node;
node = node->next;
}
}
if (cleaned_up) {
printf("Optimizer: Dead code cleanup finished, adjusted IR links.\n");
}
return optimized || cleaned_up; // 如果有指令被标记或实际删除,则认为发生了优化
}
/**
* @brief 执行窥孔优化。
* 检查一小段(“窥孔”)连续的指令序列,用更短、更快或更有效的等价指令序列替换它们。
* 这是一个局部优化,不涉及复杂的数据流分析。
*
* 窥孔优化示例:
* 1. 移除冗余赋值: ASSIGN t1, t1 => NOP
* 2. 优化常量加/减/乘/除0/1:
* ADD t1, op1, 0 => ASSIGN t1, op1
* SUB t1, op1, 0 => ASSIGN t1, op1
* MUL t1, op1, 1 => ASSIGN t1, op1
* DIV t1, op1, 1 => ASSIGN t1, op1
* MUL t1, op1, 0 => ASSIGN t1, 0
* DIV t1, 0, op1 => ASSIGN t1, 0 (如果op1非零)
* 3. 简单的强度削减: MUL t1, op1, 2 => LSHIFT t1, op1, 1 (仅限整数)
* MUL t1, op1, 4 => LSHIFT t1, op1, 2
* 4. 跳转到跳转: JMP L1; ...; LABEL L1; JMP L2 => JMP L2 (直接修改JMP L1)
*
* @param head IR指令链表头。
* @return true 如果发生了优化,false 否则。
*/
bool perform_peephole_optimization(Instruction *head) {
bool optimized = false;
Instruction *current = head;
while (current != NULL) {
if (current->is_deleted) {
current = current->next;
continue;
}
// --- 1. 移除冗余赋值: ASSIGN t1, t1 ---
if (current->type == IR_ASSIGN && operand_equals(current->result, current->op1)) {
printf("Optimizer: Peephole - Removing redundant assignment at line %d (e.g., %s = %s).\n",
current->line, operand_type_to_string(current->result.type), operand_type_to_string(current->op1.type));
current->is_deleted = true;
current->type = IR_NOP; // 标记为NOP
free_operand_content(¤t->result);
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
optimized = true;
}
// --- 2. 优化常量加/减/乘/除0/1 ---
// ADD t1, op1, 0 => ASSIGN t1, op1
else if (current->type == IR_ADD && current->op2.type == OP_LITERAL_INT && current->op2.value.int_val == 0) {
printf("Optimizer: Peephole - Removing redundant add by zero at line %d.\n", current->line);
free_operand_content(¤t->op1); // 释放旧op1
free_operand_content(¤t->op2); // 释放旧op2
current->op1 = copy_operand(current->result); // 将结果操作数赋给op1,因为它是赋值的源
current->type = IR_ASSIGN;
current->op2 = create_none_operand();
optimized = true;
}
// SUB t1, op1, 0 => ASSIGN t1, op1
else if (current->type == IR_SUB && current->op2.type == OP_LITERAL_INT && current->op2.value.int_val == 0) {
printf("Optimizer: Peephole - Removing redundant sub by zero at line %d.\n", current->line);
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
current->op1 = copy_operand(current->result);
current->type = IR_ASSIGN;
current->op2 = create_none_operand();
optimized = true;
}
// MUL t1, op1, 1 => ASSIGN t1, op1
else if (current->type == IR_MUL && current->op2.type == OP_LITERAL_INT && current->op2.value.int_val == 1) {
printf("Optimizer: Peephole - Removing redundant mul by one at line %d.\n", current->line);
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
current->op1 = copy_operand(current->result);
current->type = IR_ASSIGN;
current->op2 = create_none_operand();
optimized = true;
}
// DIV t1, op1, 1 => ASSIGN t1, op1
else if (current->type == IR_DIV && current->op2.type == OP_LITERAL_INT && current->op2.value.int_val == 1) {
printf("Optimizer: Peephole - Removing redundant div by one at line %d.\n", current->line);
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
current->op1 = copy_operand(current->result);
current->type = IR_ASSIGN;
current->op2 = create_none_operand();
optimized = true;
}
// MUL t1, op1, 0 => ASSIGN t1, 0
else if (current->type == IR_MUL && current->op2.type == OP_LITERAL_INT && current->op2.value.int_val == 0) {
printf("Optimizer: Peephole - Optimizing mul by zero at line %d.\n", current->line);
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
current->op1 = create_literal_operand_int(0); // 结果直接为0
current->type = IR_ASSIGN;
current->op2 = create_none_operand();
optimized = true;
}
// --- 3. 简单的强度削减: MUL t1, op1, 2/4/8 => LSHIFT t1, op1, X ---
else if (current->type == IR_MUL && current->op1.data_type == TYPE_INT &&
current->op2.type == OP_LITERAL_INT) {
long val = current->op2.value.int_val;
if (val == 2) {
printf("Optimizer: Peephole - Strength reduction (mul by 2 to lshift by 1) at line %d.\n", current->line);
free_operand_content(¤t->op2);
current->type = IR_LSHIFT;
current->op2 = create_literal_operand_int(1);
optimized = true;
} else if (val == 4) {
printf("Optimizer: Peephole - Strength reduction (mul by 4 to lshift by 2) at line %d.\n", current->line);
free_operand_content(¤t->op2);
current->type = IR_LSHIFT;
current->op2 = create_literal_operand_int(2);
optimized = true;
} else if (val == 8) {
printf("Optimizer: Peephole - Strength reduction (mul by 8 to lshift by 3) at line %d.\n", current->line);
free_operand_content(¤t->op2);
current->type = IR_LSHIFT;
current->op2 = create_literal_operand_int(3);
optimized = true;
}
}
// --- 4. 跳转到跳转: JMP L1; ...; LABEL L1; JMP L2 ---
// 这个优化比较复杂,需要查找下一个非删除指令是否为LABEL,再检查LABEL后的指令。
// 简化:仅处理 JMP L1 后面紧跟 LABEL L1 再 JMP L2 的情况(需要prev指针和next指针的双重检查)
// 如果 current 是 JMP L1
if (current->type == IR_JMP && current->next != NULL && current->next->type == IR_LABEL) {
// L1 就是 current->op1 (JMP L1 的目标标签)
// L1_inst 就是 current->next (LABEL L1 指令)
// L2_inst 就是 current->next->next (LABEL L1 之后的指令)
if (operand_equals(current->op1, current->next->result) && // 确保JMP L1是跳到紧随其后的LABEL L1
current->next->next != NULL && (current->next->next->type == IR_JMP ||
current->next->next->type == IR_JEQ ||
current->next->next->type == IR_JNE ||
current->next->next->type == IR_JLT ||
current->next->next->type == IR_JLE ||
current->next->next->type == IR_JGT ||
current->next->next->type == IR_JGE) ) {
// 将 JMP L1 更改为 JMP L2 (L2是 L1 之后的跳转指令的目标)
Instruction *target_jump_inst = current->next->next;
printf("Optimizer: Peephole - Jump to jump optimization at line %d (from %s to %s).\n",
current->line, current->op1.value.str_val, target_jump_inst->op1.value.str_val);
free_operand_content(¤t->op1);
current->op1 = copy_operand(target_jump_inst->op1); // 复制L2标签
current->type = target_jump_inst->type; // 保持跳转类型不变 (JMP, JEQ etc.)
current->op2 = copy_operand(target_jump_inst->op2); // 复制第二个操作数(条件跳转可能需要)
// 标记中间的 LABEL L1 为删除(如果它没有其他跳转目标)
// 并且标记 target_jump_inst 也为删除,因为它被合并了
current->next->is_deleted = true; current->next->type = IR_NOP;
free_operand_content(¤t->next->result); free_operand_content(¤t->next->op1); free_operand_content(¤t->next->op2);
target_jump_inst->is_deleted = true; target_jump_inst->type = IR_NOP;
free_operand_content(&target_jump_inst->result); free_operand_content(&target_jump_inst->op1); free_operand_content(&target_jump_inst->op2);
optimized = true;
}
}
current = current->next;
}
return optimized;
}
/**
* @brief 对生成的三地址码执行一系列优化。
* 这是一个简单的优化循环,重复执行优化Pass直到没有更多的优化发生。
* 这种方法称为“迭代优化”(Iterative Optimization)。
*
* @param head 指向IR指令链表头部的指针的指针。
*/
void optimize_intermediate_code(Instruction **head) {
if (*head == NULL) {
printf("Optimizer: No intermediate code to optimize.\n");
return;
}
printf("\n--- Starting Intermediate Code Optimization ---\n");
bool changed;
int pass = 0;
do {
changed = false;
pass++;
printf("Optimizer Pass %d:\n", pass);
// 先执行常量折叠,它可以产生新的常量,为DCE创造机会
if (perform_constant_folding(*head)) {
changed = true;
printf(" - Constant Folding applied.\n");
}
// 然后执行窥孔优化,移除局部冗余和进行简单转换
if (perform_peephole_optimization(*head)) {
changed = true;
printf(" - Peephole Optimization applied.\n");
}
// 最后执行死代码消除,因为它依赖于前面优化改变后的指令活跃性
// DCE可能会实际移除指令,所以它需要双向链表的头指针
if (perform_dead_code_elimination(head)) {
changed = true;
printf(" - Dead Code Elimination applied.\n");
}
// 某些优化可能会相互创造更多机会,所以需要多次迭代
// 例如:常量折叠 -> 产生常量赋值 -> DCE移除未使用的常量赋值
// DCE移除指令 -> 可能创建新的窥孔优化机会
if (changed) {
printf(" Pass %d resulted in changes. Re-running optimizations.\n", pass);
} else {
printf(" Pass %d resulted in no further changes. Optimization complete.\n", pass);
}
} while (changed);
printf("--- Intermediate Code Optimization Complete ---\n");
}
// --- 调试辅助函数实现 ---
/**
* @brief 辅助函数:将 InstructionType 枚举转换为可读的字符串。
* @param type InstructionType值。
* @return 对应的字符串。
*/
const char *instruction_type_to_string(InstructionType type) {
switch (type) {
case IR_ADD: return "ADD";
case IR_SUB: return "SUB";
case IR_MUL: return "MUL";
case IR_DIV: return "DIV";
case IR_MOD: return "MOD";
case IR_NEG: return "NEG";
case IR_NOT: return "NOT";
case IR_BIT_NOT: return "BIT_NOT";
case IR_INC: return "INC";
case IR_DEC: return "DEC";
case IR_ASSIGN: return "ASSIGN";
case IR_EQ: return "EQ";
case IR_NE: return "NE";
case IR_LT: return "LT";
case IR_LE: return "LE";
case IR_GT: return "GT";
case IR_GE: return "GE";
case IR_AND: return "AND";
case IR_OR: return "OR";
case IR_BIT_AND: return "BIT_AND";
case IR_BIT_OR: return "BIT_OR";
case IR_BIT_XOR: return "BIT_XOR";
case IR_LSHIFT: return "LSHIFT";
case IR_RSHIFT: return "RSHIFT";
case IR_LABEL: return "LABEL";
case IR_JMP: return "JMP";
case IR_JEQ: return "JEQ";
case IR_JNE: return "JNE";
case IR_JLT: return "JLT";
case IR_JLE: return "JLE";
case IR_JGT: return "JGT";
case IR_JGE: return "JGE";
case IR_PARAM: return "PARAM";
case IR_CALL: return "CALL";
case IR_RET: return "RET";
case IR_LOAD_VAR: return "LOAD_VAR";
case IR_STORE_VAR: return "STORE_VAR";
case IR_BREAK: return "BREAK";
case IR_CONTINUE: return "CONTINUE";
case IR_NOP: return "NOP";
default: return "UNKNOWN_INST";
}
}
/**
* @brief 辅助函数:将 OperandType 枚举转换为可读的字符串。
* @param type OperandType值。
* @return 对应的字符串。
*/
const char *operand_type_to_string(OperandType type) {
switch (type) {
case OP_NONE: return "NONE";
case OP_TEMP: return "TEMP";
case OP_LITERAL_INT: return "LIT_INT";
case OP_LITERAL_FLOAT: return "LIT_FLOAT";
case OP_LITERAL_CHAR: return "LIT_CHAR";
case OP_STRING_LABEL: return "STR_LABEL";
case OP_IDENTIFIER: return "IDENTIFIER";
case OP_LABEL: return "LABEL";
default: return "UNKNOWN_OP";
}
}
/**
* @brief 辅助函数:打印单个Operand的信息。
* @param op 要打印的Operand。
*/
void print_operand(Operand op) {
switch (op.type) {
case OP_NONE:
// fprintf(stdout, "NONE "); // 通常不打印,除非调试
break;
case OP_TEMP:
case OP_IDENTIFIER:
case OP_STRING_LABEL:
case OP_LABEL:
fprintf(stdout, "%s ", op.value.str_val ? op.value.str_val : "NULL_STR");
break;
case OP_LITERAL_INT:
fprintf(stdout, "%ld ", op.value.int_val);
break;
case OP_LITERAL_FLOAT:
fprintf(stdout, "%f ", op.value.float_val);
break;
case OP_LITERAL_CHAR:
fprintf(stdout, "'%c' ", op.value.char_val);
break;
default:
fprintf(stdout, "UNKNOWN_OP ");
break;
}
}
/**
* @brief 打印生成的所有中间代码 (用于调试)。
* 会跳过被标记为 is_deleted 的指令。
*/
void print_intermediate_code() {
fprintf(stdout, "\n--- Intermediate Code (TAC) ---\n");
Instruction *current = ir_head;
int inst_idx = 0;
while (current != NULL) {
// 跳过已删除的指令
if (current->is_deleted || current->type == IR_NOP) {
current = current->next;
continue;
}
fprintf(stdout, "IR%03d (L%d,C%d): ", inst_idx++, current->line, current->col);
switch (current->type) {
case IR_ADD: case IR_SUB: case IR_MUL: case IR_DIV: case IR_MOD:
case IR_EQ: case IR_NE: case IR_LT: case IR_LE: case IR_GT: case IR_GE:
case IR_AND: case IR_OR: case IR_BIT_AND: case IR_BIT_OR: case IR_BIT_XOR:
case IR_LSHIFT: case IR_RSHIFT:
print_operand(current->result);
fprintf(stdout, "= ");
print_operand(current->op1);
fprintf(stdout, "%s ", instruction_type_to_string(current->type));
print_operand(current->op2);
break;
case IR_NEG: case IR_NOT: case IR_BIT_NOT:
print_operand(current->result);
fprintf(stdout, "= %s ", instruction_type_to_string(current->type));
print_operand(current->op1);
break;
case IR_ASSIGN:
print_operand(current->result);
fprintf(stdout, "= ");
print_operand(current->op1);
break;
case IR_INC: case IR_DEC: // ++x, --x (通常转换为 t = x + 1, x = t)
print_operand(current->result);
fprintf(stdout, "= %s ", instruction_type_to_string(current->type));
print_operand(current->op1);
break;
case IR_LABEL:
fprintf(stdout, "LABEL ");
print_operand(current->result);
break;
case IR_JMP:
fprintf(stdout, "JMP ");
print_operand(current->result);
break;
case IR_JEQ: case IR_JNE: case IR_JLT: case IR_JLE: case IR_JGT: case IR_JGE:
fprintf(stdout, "IF ");
print_operand(current->op1);
fprintf(stdout, "%s ", instruction_type_to_string(current->type));
print_operand(current->op2);
fprintf(stdout, " JMP ");
print_operand(current->result);
break;
case IR_PARAM:
fprintf(stdout, "PARAM ");
print_operand(current->result);
break;
case IR_CALL:
if (current->result.type != OP_NONE) {
print_operand(current->result);
fprintf(stdout, "= ");
}
fprintf(stdout, "CALL ");
print_operand(current->op1); // 函数名
fprintf(stdout, ", %ld args", current->op2.value.int_val); // 参数数量
break;
case IR_RET:
fprintf(stdout, "RET ");
print_operand(current->result); // 返回值(可能是NONE)
break;
case IR_LOAD_VAR:
print_operand(current->result);
fprintf(stdout, "= LOAD ");
print_operand(current->op1);
break;
case IR_STORE_VAR:
fprintf(stdout, "STORE ");
print_operand(current->result); // 变量名
fprintf(stdout, ", ");
print_operand(current->op1); // 要存储的值
break;
case IR_BREAK:
fprintf(stdout, "BREAK");
break;
case IR_CONTINUE:
fprintf(stdout, "CONTINUE");
break;
case IR_NOP:
fprintf(stdout, "NOP (Optimized Out)");
break;
default:
fprintf(stdout, "UNKNOWN_INSTRUCTION");
break;
}
fprintf(stdout, "\n");
current = current->next;
}
fprintf(stdout, "--- End of Intermediate Code ---\n");
}
/**
* @brief 释放所有中间代码指令占用的内存。
* 遍历IR链表,释放每个Instruction结构体及其内部动态分配的操作数内存。
* 会根据操作数的 is_freed 标志避免重复释放,防止双重释放错误。
*/
void free_intermediate_code() {
Instruction *current = ir_head;
while (current != NULL) {
Instruction *next_inst = current->next;
// 释放操作数内容
free_operand_content(¤t->result);
free_operand_content(¤t->op1);
free_operand_content(¤t->op2);
// 释放指令结构体本身
free(current);
current = next_inst;
}
ir_head = NULL;
ir_tail = NULL;
printf("Intermediate code memory freed.\n");
}
------------------------------------------------------------------------------------------------------------更新于2025.6.12号上午10:12分 重新上传于6.22号下午4点半