写在最开头:
前两天更新了手撸编译器的第一部分:上部分,接下来有时间了继续完成第二部分:


手撸编译器系列(三)—— 语义分析
引言:从“骨架”到“血肉”——代码的“意义”何在?
兄弟们,回顾一下咱们的“手撸编译器”之旅:
-
词法分析(Lexical Analysis):把C代码的字符流,分解成一个个有意义的“词法单元”(Token)。这是编译器的“眼睛”,能识别出
int、main、123这些“单词”。 -
语法分析(Syntax Analysis):把Token流组织成一棵“抽象语法树”(AST)。这是编译器的“大脑”,能理解代码的“骨架”结构,比如
int a = 10;是一个变量声明语句,a + b是一个二元表达式。
现在,咱们的编译器已经能“看懂”C代码的结构了。但是,结构正确不代表逻辑正确啊!
举个栗子:
int main() {
int my_variable; // 声明一个整数变量
my_variable = "Hello World"; // 尝试把字符串赋值给整数变量
undeclared_variable = 10; // 使用一个未声明的变量
return 0;
}
这段代码:
-
从词法上看,所有的Token(
int,main,my_variable,=,"Hello World",;等)都是合法的。 -
从语法上看,
int my_variable;是一个合法的声明语句,my_variable = "Hello World";和undeclared_variable = 10;也是合法的赋值语句结构。
但是!从语义上看,这段代码错得离谱!
-
你不能把一个字符串赋值给一个整数变量,这叫类型不匹配!
-
undeclared_variable压根就没声明过,你直接用它,这叫未声明标识符!
这就是语义分析(Semantic Analysis)要干的活儿!它是编译器的“火眼金睛”,负责检查代码的逻辑正确性和含义合法性。它要确保你的代码不仅仅是“文法通顺”,更是“言之有理”!
语义分析的“火眼金睛”:它到底看什么?
语义分析器主要关注以下几个方面:
1. 作用域管理(Scope Management):变量的“户口本”
在C语言中,变量的可见范围和生命周期是由其**作用域(Scope)**决定的。
-
全局作用域:在所有函数之外声明的变量,在整个程序中都可见。
-
函数作用域:函数内部声明的参数和局部变量,只在该函数内部可见。
-
块作用域:在
if、while、for语句的代码块{}内声明的变量,只在该代码块内可见。
语义分析器需要跟踪当前所处的作用域,以便正确地解析标识符(变量名、函数名等)。当你在代码中引用一个变量时,语义分析器会从当前作用域开始,逐级向外查找,直到找到该变量的声明。如果找不到,就报告“未声明标识符”错误。
2. 类型检查(Type Checking):代码的“血型”匹配
C语言是强类型语言,这意味着每个变量和表达式都有一个确定的类型。语义分析器会检查:
-
赋值兼容性:
int a; a = 10;合法,int a; a = "hello";不合法。 -
操作符类型匹配:
a + b,如果a和b都是整数,结果是整数;如果一个是整数一个是字符串,那通常是错误。 -
函数调用类型匹配:函数参数的类型和数量是否与函数定义匹配。
-
返回类型匹配:
return语句中的表达式类型是否与函数声明的返回类型匹配。
3. 声明与定义检查:名字的“合法性”
-
重复定义:同一个作用域内,不能声明两个同名的变量或函数。
-
使用前声明:所有使用的标识符都必须在使用前声明。
-
函数定义检查:确保函数在被调用时已经被定义(或者至少有声明)。
4. 其他语义检查(根据语言特性)
-
控制流检查:例如,
break和continue语句是否在循环或switch语句内部。 -
可达性检查:例如,函数体中
return语句后的代码是否可达。 -
左值检查:赋值操作符的左侧必须是可修改的左值(例如,
10 = a;是非法的)。
符号表(Symbol Table):编译器的“记忆力”
要实现上述语义检查,语义分析器必须有一个强大的“记忆力”,能够记住所有声明过的标识符及其属性。这个“记忆力”的核心就是符号表(Symbol Table)。
什么是符号表?
符号表是一个数据结构,用于存储源代码中所有标识符(Identifier)的信息。每个标识符在符号表中都对应一个符号项(Symbol Entry),其中包含:
-
名称(Name):标识符的字符串名称(如
my_variable)。 -
类型(Type):标识符的类型(如
int、void、函数类型等)。 -
作用域(Scope):标识符所属的作用域,用于区分同名但不同作用域的标识符。
-
存储位置/地址(Address/Offset):在内存中的相对或绝对地址(在代码生成阶段会用到)。
-
其他属性:例如,是否为常量、是否为函数、函数参数列表、数组大小等。
为什么符号表要用“栈式”结构?
C语言的作用域是嵌套的,而且是块作用域。为了正确处理这种嵌套关系和标识符的可见性,符号表通常采用**栈式(Stack-based)**结构。
(这里想象一个栈式符号表的图: 栈顶是当前作用域的哈希表,栈底是全局作用域的哈希表。 当进入一个新作用域(如函数体、if 语句块)时,一个新的哈希表会被压入栈顶。 当退出一个作用域时,栈顶的哈希表会被弹出。 查找符号时,从栈顶(当前作用域)开始向上查找,直到找到或到达栈底。 )
具体实现通常是:
-
每个作用域对应一个独立的哈希表(Hash Table)。
-
这些哈希表被组织成一个栈。
-
进入新作用域:创建一个新的哈希表,并将其压入栈顶。
-
退出作用域:弹出栈顶的哈希表。
-
添加符号:将符号添加到当前作用域(栈顶哈希表)。
-
查找符号:从栈顶开始,逐级向下(向外层作用域)查找,直到找到第一个匹配的符号,或者遍历完所有作用域。这种查找顺序自然地实现了局部变量遮蔽全局变量的规则。
抽象语法树(AST)的增强与遍历
语义分析的核心工作就是遍历AST。在遍历过程中,它会:
-
构建和维护符号表:在进入新的作用域节点(如函数声明、复合语句)时,压入新的符号表;在退出时,弹出符号表。在声明节点(如变量声明)时,向当前符号表添加符号。
-
执行语义检查:根据AST节点的类型,执行相应的类型检查、声明检查等。
-
向AST节点添加语义信息:例如,为标识符节点添加指向其在符号表中对应项的指针,或者为表达式节点添加其推断出的类型信息。这被称为AST的修饰(Annotation)。
AST节点类型增强(ast.h 变更)
为了存储类型信息,我们需要在 ASTNode 结构体中添加一个 data_type 字段。
// ast.h (更新部分)
// ... (其他原有定义)
// AST节点基类(所有具体AST节点的通用部分)
// 这是一个联合体,可以存储不同类型的AST节点数据
typedef struct ASTNode {
ASTNodeType type; // 节点的类型
// 新增:用于存储该AST节点(特别是表达式节点)的推断类型
// 在语义分析阶段,这个字段会被填充
char *data_type; // 例如 "int", "void"
// 联合体,根据type字段存储不同的节点数据
union {
// ... (原有数据联合体)
} data;
} ASTNode;
// ... (其他原有定义)
// AST节点创建辅助函数 (需要更新以初始化data_type)
// ASTNode *create_ast_node(ASTNodeType type); // 这个函数需要更新
// ... (其他原有定义)
符号表数据结构(symbol_table.h 和 symbol_table.c)
我们将单独创建 symbol_table.h 和 symbol_table.c 来管理符号表。
symbol_table.h:符号表接口定义
#ifndef SYMBOL_TABLE_H
#define SYMBOL_TABLE_H
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// 符号项的类型
typedef enum {
SYM_TYPE_UNKNOWN,
SYM_TYPE_INT,
SYM_TYPE_VOID,
// SYM_TYPE_CHAR, // 后续可扩展
// SYM_TYPE_FUNCTION, // 后续可扩展函数类型
} SymbolDataType;
// 符号项结构体
typedef struct SymbolEntry {
char *name; // 符号名称 (e.g., "my_variable", "main")
SymbolDataType type; // 符号的数据类型 (e.g., SYM_TYPE_INT, SYM_TYPE_VOID)
// int scope_level; // 符号所在的作用域级别 (用于调试,可选)
// int address_offset; // 符号在内存中的偏移量 (代码生成阶段使用)
// 其他属性,如函数参数列表、数组大小等
struct SymbolEntry *next; // 用于哈希表链表冲突解决
} SymbolEntry;
// 哈希表结构体 (每个作用域一个哈希表)
typedef struct HashTable {
SymbolEntry **buckets; // 哈希桶数组
int capacity; // 哈希表容量
int size; // 当前存储的符号数量
} HashTable;
// 作用域结构体
typedef struct Scope {
HashTable *table; // 当前作用域的哈希表
struct Scope *parent; // 指向父作用域 (用于作用域链查找)
// int level; // 作用域级别 (用于调试,可选)
} Scope;
// 全局作用域栈顶指针
extern Scope *current_scope_g;
// -----------------------------------------------------------------------------
// 符号表操作函数
// -----------------------------------------------------------------------------
// 初始化符号表 (创建全局作用域)
void symbol_table_init();
// 进入新的作用域
void enter_scope();
// 退出当前作用域
void exit_scope();
// 向当前作用域添加一个符号
// 参数:name - 符号名称
// type - 符号数据类型
// 返回值:成功返回true,如果符号已存在则返回false
bool add_symbol(const char *name, SymbolDataType type);
// 在当前作用域链中查找符号
// 从当前作用域开始,逐级向上查找
// 参数:name - 待查找的符号名称
// 返回值:如果找到,返回指向SymbolEntry的指针;否则返回NULL
SymbolEntry *lookup_symbol(const char *name);
// 释放符号表所有内存
void symbol_table_cleanup();
// 辅助函数:将SymbolDataType转换为可读字符串
const char *symbol_data_type_to_string(SymbolDataType type);
#endif // SYMBOL_TABLE_H
symbol_table.c:符号表实现
#include "symbol_table.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 全局作用域栈顶指针
Scope *current_scope_g = NULL;
// 哈希函数 (简单的DJB2哈希算法)
static unsigned int hash_string(const char *str, int capacity) {
unsigned int hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash % capacity;
}
// -----------------------------------------------------------------------------
// 哈希表操作
// -----------------------------------------------------------------------------
// 创建一个新的哈希表
static HashTable *create_hash_table(int capacity) {
HashTable *table = (HashTable *)malloc(sizeof(HashTable));
if (!table) {
fprintf(stderr, "Error: Failed to allocate memory for HashTable.\n");
exit(EXIT_FAILURE);
}
table->capacity = capacity;
table->size = 0;
table->buckets = (SymbolEntry **)calloc(capacity, sizeof(SymbolEntry *)); // 初始化为NULL
if (!table->buckets) {
fprintf(stderr, "Error: Failed to allocate memory for HashTable buckets.\n");
free(table);
exit(EXIT_FAILURE);
}
return table;
}
// 释放哈希表内存
static void free_hash_table(HashTable *table) {
if (!table) return;
for (int i = 0; i < table->capacity; ++i) {
SymbolEntry *entry = table->buckets[i];
while (entry) {
SymbolEntry *temp = entry;
entry = entry->next;
free(temp->name); // 释放符号名称字符串
free(temp); // 释放符号项
}
}
free(table->buckets); // 释放桶数组
free(table); // 释放哈希表结构体
}
// 向哈希表添加符号 (只在当前哈希表查找,不向上查找作用域链)
static bool hash_table_add(HashTable *table, const char *name, SymbolDataType type) {
unsigned int index = hash_string(name, table->capacity);
SymbolEntry *entry = table->buckets[index];
// 检查是否已存在同名符号(在当前作用域内)
while (entry) {
if (strcmp(entry->name, name) == 0) {
return false; // 符号已存在
}
entry = entry->next;
}
// 创建新的符号项
SymbolEntry *new_entry = (SymbolEntry *)malloc(sizeof(SymbolEntry));
if (!new_entry) {
fprintf(stderr, "Error: Failed to allocate memory for SymbolEntry.\n");
exit(EXIT_FAILURE);
}
new_entry->name = strdup(name);
new_entry->type = type;
new_entry->next = table->buckets[index]; // 插入到链表头部
table->buckets[index] = new_entry;
table->size++;
return true;
}
// 在哈希表中查找符号 (只在当前哈希表查找)
static SymbolEntry *hash_table_lookup(HashTable *table, const char *name) {
unsigned int index = hash_string(name, table->capacity);
SymbolEntry *entry = table->buckets[index];
while (entry) {
if (strcmp(entry->name, name) == 0) {
return entry; // 找到符号
}
entry = entry->next;
}
return NULL; // 未找到
}
// -----------------------------------------------------------------------------
// 符号表(作用域)操作
// -----------------------------------------------------------------------------
// 初始化符号表 (创建全局作用域)
void symbol_table_init() {
// 初始容量设为16,可根据需要调整
current_scope_g = (Scope *)malloc(sizeof(Scope));
if (!current_scope_g) {
fprintf(stderr, "Error: Failed to allocate memory for global scope.\n");
exit(EXIT_FAILURE);
}
current_scope_g->table = create_hash_table(16);
current_scope_g->parent = NULL; // 全局作用域没有父作用域
// current_scope_g->level = 0; // 级别为0
fprintf(stdout, "Symbol table initialized. Global scope created.\n");
}
// 进入新的作用域
void enter_scope() {
Scope *new_scope = (Scope *)malloc(sizeof(Scope));
if (!new_scope) {
fprintf(stderr, "Error: Failed to allocate memory for new scope.\n");
exit(EXIT_FAILURE);
}
new_scope->table = create_hash_table(16); // 新作用域的哈希表
new_scope->parent = current_scope_g; // 新作用域的父作用域是当前作用域
// new_scope->level = current_scope_g ? current_scope_g->level + 1 : 0;
current_scope_g = new_scope; // 更新当前作用域为新作用域
fprintf(stdout, "Entered new scope.\n");
}
// 退出当前作用域
void exit_scope() {
if (current_scope_g == NULL) {
fprintf(stderr, "Error: Attempted to exit scope when no scope is active.\n");
exit(EXIT_FAILURE);
}
Scope *old_scope = current_scope_g;
current_scope_g = old_scope->parent; // 恢复到父作用域
free_hash_table(old_scope->table); // 释放当前作用域的哈希表内存
free(old_scope); // 释放当前作用域结构体内存
fprintf(stdout, "Exited scope.\n");
}
// 向当前作用域添加一个符号
bool add_symbol(const char *name, SymbolDataType type) {
if (current_scope_g == NULL) {
fprintf(stderr, "Error: Cannot add symbol, no active scope.\n");
exit(EXIT_FAILURE);
}
// 只在当前作用域的哈希表中添加,不向上查找
return hash_table_add(current_scope_g->table, name, type);
}
// 在当前作用域链中查找符号
SymbolEntry *lookup_symbol(const char *name) {
Scope *scope_ptr = current_scope_g;
while (scope_ptr) {
SymbolEntry *entry = hash_table_lookup(scope_ptr->table, name);
if (entry) {
return entry; // 找到符号
}
scope_ptr = scope_ptr->parent; // 在父作用域中继续查找
}
return NULL; // 未找到符号
}
// 释放符号表所有内存 (从全局作用域开始释放)
void symbol_table_cleanup() {
while (current_scope_g) {
exit_scope(); // 逐个退出并释放作用域
}
fprintf(stdout, "Symbol table cleaned up.\n");
}
// 辅助函数:将SymbolDataType转换为可读字符串
const char *symbol_data_type_to_string(SymbolDataType type) {
switch (type) {
case SYM_TYPE_INT: return "int";
case SYM_TYPE_VOID: return "void";
case SYM_TYPE_UNKNOWN: return "unknown";
default: return "UNKNOWN_SYM_TYPE";
}
}
语义分析器(semantic_analyzer.h 和 semantic_analyzer.c)
现在,我们来编写语义分析器的核心逻辑。
semantic_analyzer.h:语义分析器接口定义
#ifndef SEMANTIC_ANALYZER_H
#define SEMANTIC_ANALYZER_H
#include "ast.h" // 需要AST节点定义
#include "symbol_table.h" // 需要符号表操作
// 语义分析的入口函数
// 参数:program_ast - 语法分析器生成的AST根节点
// 返回值:如果语义分析成功,返回true;否则返回false
bool semantic_analyze_program(ASTNode *program_ast);
// 辅助函数:将ASTNode的data_type转换为SymbolDataType
SymbolDataType ast_node_type_to_symbol_data_type(const char *type_name);
#endif // SEMANTIC_ANALYZER_H
semantic_analyzer.c:语义分析器实现
#include "semantic_analyzer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// -----------------------------------------------------------------------------
// 辅助函数:类型转换
// -----------------------------------------------------------------------------
// 将ASTNode的data_type字符串转换为SymbolDataType枚举
SymbolDataType ast_node_type_to_symbol_data_type(const char *type_name) {
if (strcmp(type_name, "int") == 0) {
return SYM_TYPE_INT;
} else if (strcmp(type_name, "void") == 0) {
return SYM_TYPE_VOID;
}
return SYM_TYPE_UNKNOWN;
}
// -----------------------------------------------------------------------------
// 语义分析核心函数 (递归遍历AST)
// -----------------------------------------------------------------------------
// 前向声明所有语义分析函数
static bool analyze_function_declaration(ASTNode *node);
static bool analyze_compound_statement(ASTNode *node);
static bool analyze_statement(ASTNode *node);
static bool analyze_variable_declaration(ASTNode *node);
static bool analyze_return_statement(ASTNode *node);
static bool analyze_if_statement(ASTNode *node);
static bool analyze_while_statement(ASTNode *node);
static bool analyze_expression_statement(ASTNode *node);
static SymbolDataType analyze_expression(ASTNode *node); // 表达式分析返回其类型
// 语义错误报告函数
static void semantic_error(const char *message) {
fprintf(stderr, "Semantic Error: %s\n", message);
// 实际编译器可能收集所有错误,这里简单退出
exit(EXIT_FAILURE);
}
// 分析函数声明
static bool analyze_function_declaration(ASTNode *node) {
if (node->type != AST_FUNCTION_DECLARATION) {
semantic_error("Expected AST_FUNCTION_DECLARATION node.");
return false;
}
ASTFunctionDeclaration *func_decl = node->data.func_decl;
SymbolDataType return_sym_type = ast_node_type_to_symbol_data_type(func_decl->return_type);
// 1. 将函数名添加到全局作用域的符号表 (函数通常是全局可见的)
// 注意:这里我们简化处理,所有函数都视为全局符号
// 并且暂时不存储函数参数信息,后续可扩展
if (!add_symbol(func_decl->name, return_sym_type)) {
semantic_error("Function redefinition: Duplicate function name '%s'.", func_decl->name);
return false;
}
fprintf(stdout, "Semantic: Declared function '%s' with return type '%s'.\n", func_decl->name, func_decl->return_type);
// 2. 进入新的作用域,用于函数参数和局部变量
enter_scope();
// 3. 递归分析函数体
bool success = analyze_compound_statement(func_decl->body);
// 4. 退出函数作用域
exit_scope();
return success;
}
// 分析复合语句 (代码块)
static bool analyze_compound_statement(ASTNode *node) {
if (node->type != AST_COMPOUND_STATEMENT) {
semantic_error("Expected AST_COMPOUND_STATEMENT node.");
return false;
}
ASTCompoundStatement *compound_stmt = node->data.compound_stmt;
// 进入新的块作用域
enter_scope();
// 遍历并分析所有语句
for (int i = 0; i < compound_stmt->num_statements; ++i) {
if (!analyze_statement(compound_stmt->statements[i])) {
exit_scope(); // 遇到错误,提前退出作用域并返回
return false;
}
}
// 退出当前块作用域
exit_scope();
return true;
}
// 分析语句 (分发函数)
static bool analyze_statement(ASTNode *node) {
if (!node) return true; // 空语句或无效节点
switch (node->type) {
case AST_VARIABLE_DECLARATION:
return analyze_variable_declaration(node);
case AST_RETURN_STATEMENT:
return analyze_return_statement(node);
case AST_IF_STATEMENT:
return analyze_if_statement(node);
case AST_WHILE_STATEMENT:
return analyze_while_statement(node);
case AST_EXPRESSION_STATEMENT:
return analyze_expression_statement(node);
case AST_COMPOUND_STATEMENT:
return analyze_compound_statement(node); // 复合语句可以作为子语句
default:
semantic_error("Unhandled statement type in semantic analysis.");
return false;
}
}
// 分析变量声明
static bool analyze_variable_declaration(ASTNode *node) {
if (node->type != AST_VARIABLE_DECLARATION) {
semantic_error("Expected AST_VARIABLE_DECLARATION node.");
return false;
}
ASTVariableDeclaration *var_decl = node->data.var_decl;
SymbolDataType var_sym_type = ast_node_type_to_symbol_data_type(var_decl->type);
// 1. 将变量添加到当前作用域的符号表
if (!add_symbol(var_decl->name, var_sym_type)) {
semantic_error("Variable redefinition: Duplicate variable name '%s' in current scope.", var_decl->name);
return false;
}
fprintf(stdout, "Semantic: Declared variable '%s' with type '%s'.\n", var_decl->name, var_decl->type);
// 2. 如果有初始化表达式,分析初始化表达式并进行类型检查
if (var_decl->initializer) {
SymbolDataType initializer_type = analyze_expression(var_decl->initializer);
if (initializer_type == SYM_TYPE_UNKNOWN) {
semantic_error("Invalid type for initializer of variable '%s'.", var_decl->name);
return false;
}
// 简单的类型兼容性检查:目前只支持int和void,void不能用于变量
if (var_sym_type == SYM_TYPE_INT && initializer_type == SYM_TYPE_INT) {
// 类型匹配
} else {
semantic_error("Type mismatch in variable initialization: variable '%s' is '%s', initializer is '%s'.",
var_decl->name,
symbol_data_type_to_string(var_sym_type),
symbol_data_type_to_string(initializer_type));
return false;
}
}
return true;
}
// 分析返回语句
static bool analyze_return_statement(ASTNode *node) {
if (node->type != AST_RETURN_STATEMENT) {
semantic_error("Expected AST_RETURN_STATEMENT node.");
return false;
}
ASTReturnStatement *return_stmt = node->data.return_stmt;
SymbolDataType return_expr_type = SYM_TYPE_VOID; // 默认返回void
if (return_stmt->expression) {
return_expr_type = analyze_expression(return_stmt->expression);
if (return_expr_type == SYM_TYPE_UNKNOWN) {
semantic_error("Invalid return expression type.");
return false;
}
}
// TODO: 在这里需要获取当前函数的返回类型,并与return_expr_type进行比较
// 目前我们没有在符号表中存储当前函数的信息,所以无法做精确检查
// 这是一个后续需要改进的地方,例如在enter_scope时将函数信息压入栈
fprintf(stdout, "Semantic: Analyzed return statement with expression type '%s'. (Function return type check skipped for now)\n",
symbol_data_type_to_string(return_expr_type));
return true;
}
// 分析 if 语句
static bool analyze_if_statement(ASTNode *node) {
if (node->type != AST_IF_STATEMENT) {
semantic_error("Expected AST_IF_STATEMENT node.");
return false;
}
ASTIfStatement *if_stmt = node->data.if_stmt;
// 1. 分析条件表达式
SymbolDataType condition_type = analyze_expression(if_stmt->condition);
if (condition_type == SYM_TYPE_UNKNOWN || condition_type == SYM_TYPE_VOID) {
semantic_error("If statement condition must be an integer type (or convertible to bool).");
return false;
}
// 2. 分析 then 分支语句
if (!analyze_statement(if_stmt->then_statement)) {
return false;
}
// 3. 分析 else 分支语句 (如果存在)
if (if_stmt->else_statement) {
if (!analyze_statement(if_stmt->else_statement)) {
return false;
}
}
return true;
}
// 分析 while 语句
static bool analyze_while_statement(ASTNode *node) {
if (node->type != AST_WHILE_STATEMENT) {
semantic_error("Expected AST_WHILE_STATEMENT node.");
return false;
}
ASTWhileStatement *while_stmt = node->data.while_stmt;
// 1. 分析条件表达式
SymbolDataType condition_type = analyze_expression(while_stmt->condition);
if (condition_type == SYM_TYPE_UNKNOWN || condition_type == SYM_TYPE_VOID) {
semantic_error("While loop condition must be an integer type (or convertible to bool).");
return false;
}
// 2. 分析循环体语句
if (!analyze_statement(while_stmt->body)) {
return false;
}
return true;
}
// 分析表达式语句
static bool analyze_expression_statement(ASTNode *node) {
if (node->type != AST_EXPRESSION_STATEMENT) {
semantic_error("Expected AST_EXPRESSION_STATEMENT node.");
return false;
}
ASTExpressionStatement *expr_stmt = node->data.expr_stmt;
if (expr_stmt->expression) {
// 分析表达式,但其返回类型不重要,因为是语句
analyze_expression(expr_stmt->expression);
}
return true;
}
// 分析表达式,并返回其推断类型
static SymbolDataType analyze_expression(ASTNode *node) {
if (!node) {
semantic_error("Null expression node encountered.");
return SYM_TYPE_UNKNOWN;
}
SymbolDataType inferred_type = SYM_TYPE_UNKNOWN;
switch (node->type) {
case AST_INT_LITERAL:
inferred_type = SYM_TYPE_INT;
node->data_type = strdup(symbol_data_type_to_string(inferred_type)); // 标注类型到AST节点
break;
case AST_STRING_LITERAL:
// 字符串字面量在C中是 char[] 类型,这里简化为 unknown 或特殊类型
inferred_type = SYM_TYPE_UNKNOWN; // 暂时标记为UNKNOWN,后续可以扩展为SYM_TYPE_CHAR_ARRAY
node->data_type = strdup("char*"); // 标注类型到AST节点
break;
case AST_IDENTIFIER: {
ASTIdentifier *identifier = node->data.identifier;
SymbolEntry *entry = lookup_symbol(identifier->name);
if (!entry) {
semantic_error("Undeclared identifier '%s'.", identifier->name);
return SYM_TYPE_UNKNOWN;
}
inferred_type = entry->type;
node->data_type = strdup(symbol_data_type_to_string(inferred_type)); // 标注类型到AST节点
fprintf(stdout, "Semantic: Found identifier '%s' with type '%s'.\n", identifier->name, node->data_type);
break;
}
case AST_BINARY_EXPRESSION: {
ASTBinaryExpression *binary_expr = node->data.binary_expr;
SymbolDataType left_type = analyze_expression(binary_expr->left);
SymbolDataType right_type = analyze_expression(binary_expr->right);
// 简单的二元操作类型检查:目前只支持int op int -> int
if (left_type != SYM_TYPE_INT || right_type != SYM_TYPE_INT) {
semantic_error("Type mismatch in binary expression: expected 'int %s int', got '%s %s %s'.",
operator_type_to_string(binary_expr->op),
symbol_data_type_to_string(left_type),
operator_type_to_string(binary_expr->op),
symbol_data_type_to_string(right_type));
return SYM_TYPE_UNKNOWN;
}
// 对于算术、关系、逻辑运算符,结果通常是int (C语言中非0为真)
inferred_type = SYM_TYPE_INT;
node->data_type = strdup(symbol_data_type_to_string(inferred_type)); // 标注类型到AST节点
break;
}
case AST_UNARY_EXPRESSION: {
ASTUnaryExpression *unary_expr = node->data.unary_expr;
SymbolDataType operand_type = analyze_expression(unary_expr->operand);
// 简单的一元操作类型检查
if (operand_type != SYM_TYPE_INT) {
semantic_error("Type mismatch in unary expression: expected '%s int', got '%s %s'.",
operator_type_to_string(unary_expr->op),
operator_type_to_string(unary_expr->op),
symbol_data_type_to_string(operand_type));
return SYM_TYPE_UNKNOWN;
}
inferred_type = SYM_TYPE_INT;
node->data_type = strdup(symbol_data_type_to_string(inferred_type)); // 标注类型到AST节点
break;
}
case AST_ASSIGNMENT_EXPRESSION: {
ASTAssignmentExpression *assign_expr = node->data.assign_expr;
SymbolDataType left_type = analyze_expression(assign_expr->left); // 左侧通常是标识符
SymbolDataType right_type = analyze_expression(assign_expr->right);
// 检查左侧是否是可修改的左值 (目前只检查是否是标识符)
if (assign_expr->left->type != AST_IDENTIFIER) {
semantic_error("Left-hand side of assignment must be a modifiable lvalue.");
return SYM_TYPE_UNKNOWN;
}
// 简单的赋值类型兼容性检查
if (left_type != SYM_TYPE_INT || right_type != SYM_TYPE_INT) {
semantic_error("Type mismatch in assignment: expected '%s %s %s', got '%s %s %s'.",
symbol_data_type_to_string(left_type),
operator_type_to_string(assign_expr->op),
symbol_data_type_to_string(right_type),
symbol_data_type_to_string(left_type),
operator_type_to_string(assign_expr->op),
symbol_data_type_to_string(right_type));
return SYM_TYPE_UNKNOWN;
}
inferred_type = left_type; // 赋值表达式的结果类型是左侧的类型
node->data_type = strdup(symbol_data_type_to_string(inferred_type)); // 标注类型到AST节点
break;
}
default:
semantic_error("Unhandled expression type in semantic analysis.");
return SYM_TYPE_UNKNOWN;
}
return inferred_type;
}
// 语义分析的入口函数
bool semantic_analyze_program(ASTNode *program_ast) {
if (program_ast->type != AST_PROGRAM) {
semantic_error("Expected AST_PROGRAM as root node for semantic analysis.");
return false;
}
// 初始化符号表 (创建全局作用域)
symbol_table_init();
ASTProgram *program = program_ast->data.program;
// 遍历所有函数声明并进行语义分析
for (int i = 0; i < program->num_function_declarations; ++i) {
if (!analyze_function_declaration(program->function_declarations[i])) {
// 遇到错误,清理符号表并返回
symbol_table_cleanup();
return false;
}
}
// 语义分析成功,清理符号表
symbol_table_cleanup();
return true;
}
main.c:集成语义分析器
现在,咱们更新 main.c,在语法分析之后,调用语义分析器。
#include "lexer.h"
#include "parser.h"
#include "ast.h"
#include "semantic_analyzer.h" // 包含语义分析器头文件
#include <stdio.h>
#include <stdlib.h>
// main 函数现在将负责初始化词法分析器和语法分析器,然后调用解析器来构建AST
int main(int argc, char *argv[]) {
// 检查命令行参数,确保用户提供了源代码文件路径
if (argc != 2) {
fprintf(stderr, "Usage: %s <source_file.c>\n", argv[0]);
return EXIT_FAILURE;
}
const char *source_file_path = argv[1];
// 1. 初始化词法分析器
if (!lexer_init(source_file_path)) {
fprintf(stderr, "Failed to initialize lexer for file: %s\n", source_file_path);
return EXIT_FAILURE;
}
// 2. 初始化语法分析器 (它会自动获取第一个Token)
parser_init();
// 3. 开始解析整个程序,构建AST
printf("\n--- Starting Syntax Analysis ---\n");
ASTNode *program_ast = parse_program();
printf("--- Syntax Analysis Completed ---\n");
// 4. 打印生成的AST (用于调试和验证)
printf("\n--- Generated Abstract Syntax Tree (AST) ---\n");
print_ast(program_ast, 0); // 从根节点开始打印,初始缩进为0
printf("--------------------------------------------\n");
// 5. 开始语义分析
printf("\n--- Starting Semantic Analysis ---\n");
if (semantic_analyze_program(program_ast)) {
printf("--- Semantic Analysis Completed Successfully! ---\n");
} else {
fprintf(stderr, "--- Semantic Analysis Failed! ---\n");
// 语义分析失败,清理资源并退出
free_ast_node(program_ast);
lexer_cleanup();
parser_cleanup();
return EXIT_FAILURE;
}
// 6. 清理AST内存
printf("\n--- Cleaning up AST memory ---\n");
free_ast_node(program_ast);
printf("--- AST memory cleaned up ---\n");
// 7. 清理词法分析器资源
lexer_cleanup();
// 8. 清理解析器资源 (主要是释放最后一个Token)
parser_cleanup();
printf("\nCompilation phase (Lexical, Syntax, and Semantic Analysis) completed successfully.\n");
return EXIT_SUCCESS;
}
示例C语言源代码文件:test.c (更新,包含语义错误)
为了测试我们的语义分析器,咱们创建一个新的 test.c 文件,故意包含一些语义错误:
// test.c
int global_var = 10; // 全局变量
int sum(int a, int b) { // 简化处理,参数类型和名称在AST中暂时不体现
int result = a + b; // a, b 视为已声明的int
if (result > 10) {
int temp_var = 5; // 块作用域变量
result = result * temp_var;
} else {
result = result - 5;
}
while (result < 0) {
result++;
int loop_var = 1; // 循环体内的块作用域变量
loop_var = loop_var + 1;
}
return result;
}
void main() {
int x = 10;
int y = 20;
int z;
z = x + y * 2; // 合法赋值
// 语义错误示例 1: 未声明变量
// undeclared_var = 100; // 这行会引发语义错误:Undeclared identifier 'undeclared_var'.
// 语义错误示例 2: 类型不匹配的赋值
// x = "hello"; // 这行会引发语义错误:Type mismatch in assignment.
if (z == 50 || x != 10) {
int x = 5; // 语义错误示例 3: 重复定义 (在当前作用域内) - 实际C语言允许块内同名遮蔽外部变量,但我们这里简化为错误
// 如果我们允许遮蔽,这里不会报错,但会有一个新的x
z += 100;
}
x++; // Increment operator
y--; // Decrement operator
// 语义错误示例 4: 函数返回类型与return表达式不匹配 (如果 sum 返回int,这里返回void)
// return "error"; // 这行会引发语义错误 (如果函数返回int)
// int final_val = sum(x, y); // 当前解析器不支持函数调用参数,这里会简化处理
printf("Final value: %d\n", x); // printf 函数调用简化处理,只分析其表达式
return;
}
int another_func() {
// int global_var = 5; // 语义错误示例 5: 全局变量重复定义 (如果我们在全局作用域内再次定义)
return 0;
}
重要说明:
-
函数参数简化:目前我们的语法分析器和语义分析器对函数参数的处理非常简化,
int sum(int a, int b)中的a和b不会被作为正式的参数声明添加到符号表中。在sum函数内部,a和b如果被使用,当前版本会因为找不到声明而报错。这是为了简化本阶段的实现,后续可以扩展函数参数解析和符号表管理。 -
printf函数:printf("Final value: %d\n", x);这样的函数调用,目前只会被解析为一个AST_EXPRESSION_STATEMENT,其内部的字符串字面量和变量x会被分析,但printf本身不会被识别为一个预定义函数,也不会检查其参数类型。这属于更高级的语义分析范畴。 -
变量遮蔽:在
main函数的if块中,int x = 5;在标准C中是合法的,它会创建一个新的局部变量x遮蔽外部的x。但为了简化符号表查找和重复定义检查,我们目前的add_symbol函数在当前作用域发现同名就会报错。这是手撸编译器简化处理的一个例子,实际编译器会更精细地处理作用域嵌套和变量遮蔽。
编译和运行
-
保存文件:将上述代码块分别保存为
ast.h,ast.c,symbol_table.h,symbol_table.c,semantic_analyzer.h,semantic_analyzer.c,lexer.h,lexer.c和test.c。确保所有文件都是最新版本。 -
编译:在终端中使用GCC编译(确保你安装了GCC):
gcc -o mycompiler main.c lexer.c parser.c ast.c symbol_table.c semantic_analyzer.c -Wall -Wextra -std=c99-
-o mycompiler: 指定输出的可执行文件名为mycompiler。 -
main.c lexer.c parser.c ast.c symbol_table.c semantic_analyzer.c: 要编译的所有源文件。 -
-Wall -Wextra: 开启所有常见和额外的警告。 -
-std=c99: 使用C99标准编译。
-
-
运行:
./mycompiler test.c
当你运行 test.c 时,如果你取消注释那些带有“语义错误示例”的行,你将看到你的编译器会报告相应的语义错误,并终止执行!这说明你的编译器已经有了“火眼金睛”!
原理分析与逻辑剖析:语义分析器是如何“理解”C代码的“意思”的?
咱们的语义分析器,虽然是初级版,但它已经能执行C语言最基本的语义检查了。它的“理解”过程,完全是基于AST的遍历和符号表的维护。
1. 符号表:代码的“字典”和“户籍管理处”
-
symbol_table_init():在语义分析开始时调用,创建最外层的全局作用域。 -
enter_scope():每当语义分析器进入一个新的作用域(如函数体、任何{}代码块)时,就会调用此函数。它会创建一个新的Scope结构体,包含一个新的哈希表,并将其parent指针指向之前的current_scope_g,然后更新current_scope_g为新的作用域。这就像把一个新的“户籍登记簿”放在当前“户籍处”的上面。 -
exit_scope():每当语义分析器退出一个作用域时,就会调用此函数。它会释放当前作用域的哈希表内存,并将current_scope_g恢复到其parent作用域。这就像把当前的“户籍登记簿”收起来,回到上一级的“户籍处”。 -
add_symbol():在analyze_variable_declaration()和analyze_function_declaration()中调用。它将变量名或函数名及其类型添加到当前作用域(current_scope_g->table)的哈希表中。如果当前作用域中已经存在同名符号,则表示重复定义,add_symbol会返回false,语义分析器会报告错误。 -
lookup_symbol():在analyze_expression()中处理AST_IDENTIFIER节点时调用。它会从current_scope_g开始,沿着parent指针链向上查找,直到找到第一个匹配的符号。这种查找机制天然地实现了局部变量遮蔽外部变量的规则(尽管我们目前add_symbol在同名时直接报错,但查找机制是正确的)。如果遍历完所有作用域都找不到,则表示未声明标识符。
2. AST遍历与语义检查的结合
语义分析器通过递归函数遍历AST,并在每个节点上执行特定的语义检查:
-
semantic_analyze_program():入口函数,初始化符号表,然后遍历所有函数声明。 -
analyze_function_declaration():-
将函数名添加到全局符号表。
-
enter_scope():为函数体创建一个新的作用域。 -
递归调用
analyze_compound_statement()分析函数体。 -
exit_scope():函数体分析完成后,退出函数作用域。
-
-
analyze_compound_statement():-
enter_scope():为代码块创建一个新的作用域。 -
循环调用
analyze_statement()分析块内所有语句。 -
exit_scope():代码块分析完成后,退出块作用域。
-
-
analyze_variable_declaration():-
调用
add_symbol()将变量添加到当前作用域的符号表,并检查是否重复定义。 -
如果变量有初始化表达式,递归调用
analyze_expression()获取表达式的类型,并进行类型兼容性检查。
-
-
analyze_expression():这是语义分析中最复杂的函数之一。它会递归地分析表达式的子节点,并推断出当前表达式的类型。-
字面量(
AST_INT_LITERAL,AST_STRING_LITERAL):直接返回其固有类型。 -
标识符(
AST_IDENTIFIER):调用lookup_symbol()在符号表中查找标识符。如果找不到,报告“未声明标识符”错误;如果找到,返回其在符号表中记录的类型。 -
二元表达式(
AST_BINARY_EXPRESSION):递归分析左右操作数,获取它们的类型。然后根据操作符,检查左右类型是否兼容(例如,int + int),并推断出整个表达式的结果类型(例如,int)。 -
一元表达式(
AST_UNARY_EXPRESSION):递归分析操作数,获取其类型。然后检查操作数类型是否与一元操作符兼容。 -
赋值表达式(
AST_ASSIGNMENT_EXPRESSION):-
递归分析左右操作数,获取它们的类型。
-
左值检查:检查左操作数是否是可修改的左值(目前简化为检查是否是标识符)。
-
类型兼容性检查:检查右侧表达式的类型是否可以赋值给左侧变量的类型。
-
-
-
analyze_return_statement():如果return语句有表达式,则分析表达式的类型。(TODO:目前没有将当前函数返回类型传递给此函数,所以无法进行严格的返回类型匹配检查,这将在后续优化)。 -
analyze_if_statement()和analyze_while_statement():-
分析条件表达式,并检查其类型是否为整数类型(在C中,非0即真)。
-
递归分析
then分支和else分支(或循环体)。
-
3. AST的修饰(Annotation)
在 analyze_expression() 函数中,我们为每个表达式节点添加了 node->data_type = strdup(symbol_data_type_to_string(inferred_type)); 这一行。这意味着我们把表达式的推断类型信息**标注(Annotate)**到了AST节点上。
(这里想象一个AST节点图,比如 AST_BINARY_EXPRESSION 节点,除了 op, left, right 之外,还有一个 data_type: "int" 属性。)
这种修饰非常重要!它使得AST不仅仅是一个语法结构,更包含了丰富的语义信息。后续的中间代码生成和代码优化阶段,就可以直接从AST节点上获取这些类型信息,而无需重新计算或查找。
语义分析器工作流程(思维导图概念)
(这里想象一个流程图,描述语义分析器的主要步骤:
开始 -> symbol_table_init() (创建全局作用域) -> 遍历 AST_PROGRAM 的 function_declarations 列表 -> 对于每个 AST_FUNCTION_DECLARATION 节点: 将函数名添加到全局符号表 -> enter_scope() (进入函数作用域) -> 分析函数体 (AST_COMPOUND_STATEMENT) -> enter_scope() (进入块作用域) -> 遍历 AST_COMPOUND_STATEMENT 的 statements 列表 -> 对于每个 AST_STATEMENT 节点: 根据类型分发到 analyze_variable_declaration(), analyze_expression(), analyze_if_statement(), analyze_while_statement(), analyze_return_statement() -> 在这些分析函数中: -> 如果遇到 AST_IDENTIFIER:lookup_symbol() 检查是否声明,获取类型。 -> 如果遇到 AST_VARIABLE_DECLARATION:add_symbol() 检查重复定义,分析初始化表达式类型。 -> 如果遇到表达式:递归分析子表达式,推断类型,进行类型检查,标注类型到AST节点。 exit_scope() (退出块作用域) -> exit_scope() (退出函数作用域) -> symbol_table_cleanup() (清理符号表) -> 结束 )
这个流程图清晰地展示了语义分析器如何通过AST遍历和符号表操作,来执行各种语义检查,并为AST添加语义信息。
总结与展望:你已经看透了C语言的“意思”!
恭喜你,老铁!你已经成功地给你的C语言编译器装上了“火眼金睛”——语义分析器!
你现在应该明白:
-
语义分析器是编译器的“逻辑判断”专家,它负责检查代码的含义合法性,确保代码不仅仅是“文法通顺”,更是“言之有理”。
-
符号表是语义分析的核心,它像一个“户籍管理处”,记录所有标识符的信息,并通过栈式结构管理作用域。
-
语义分析器通过遍历AST,结合符号表,执行类型检查、声明检查、作用域管理等任务,并修饰AST,为其节点添加语义信息。
从字符流到Token流,从Token流到AST,再从AST到带有语义信息的AST,你已经一步步深入了C语言编译器的“灵魂”!你现在不仅仅是会写C代码,更是能从编译器的角度去“看透”C代码的“意思”了!是不是感觉对C语言的理解又提升了一个维度?
下一篇文章,我们将进入编译器的第四阶段——中间代码生成!我们将把这棵带有“血肉”的AST,转换成一种更接近机器语言,但又独立于具体机器的“中间代码”。这就像把你的“想法”(AST)翻译成一种“蓝图”,为最终生成机器码做准备
如果你觉得这篇文章让你彻底搞懂了语义分析,请务必点赞、收藏、转发,让更多想彻底搞懂C语言底层原理的兄弟们看到!
手撸编译器系列(四)—— 中间代码生成
引言:从“思想”到“蓝图”——编译器的“桥梁”艺术!
兄弟们,咱们的“手撸编译器”已经走到了第四站:
-
词法分析:把C代码的字符流变成“词法单元”(Token)。
-
语法分析:把Token流组织成“抽象语法树”(AST),代码的“骨架”。
-
语义分析:在AST上填充“血肉”,检查代码的逻辑和类型,并进行“修饰”。
现在,我们有了一棵经过语义检查、带有丰富类型信息的AST。这棵树已经完整地表达了C代码的“思想”和“意义”。但CPU可不认识什么AST!它只认识冰冷的二进制指令。
从高层的AST到低层的机器码之间,通常会有一个或多个中间表示(Intermediate Representation, IR)。中间代码生成阶段,就是把AST翻译成这种IR。
你可能会问:
-
为什么不直接从AST生成机器码?非要多加一个“中间”环节?
-
中间代码长什么样?它有什么用?
-
编译器是怎么把AST变成中间代码的?
今天,咱们就来彻底搞懂这些问题,并且,咱们还要亲手“手撸”一个简易的C语言中间代码生成器,让它把咱们的AST变成一份清晰的“执行蓝图”!
中间代码:编译器的“桥梁”——连接高层与底层!
中间代码(Intermediate Code)是源代码程序的一种独立于机器的表示形式。它介于源代码和目标代码之间,扮演着编译器内部的“通用语言”角色。
为什么需要中间代码?
中间代码的存在,给编译器带来了巨大的好处:
-
简化编译器结构:
-
前端(词法、语法、语义分析)负责将源代码翻译成IR。
-
后端(代码优化、目标代码生成)负责将IR翻译成目标代码。
-
这样,前端和后端可以相对独立地开发和维护。
-
如果我们要支持多种源语言(如C、Java)或多种目标机器(如x86、ARM),只需要修改相应的前端或后端,而IR本身保持不变,大大提高了编译器的可重用性。
-
-
方便代码优化:
-
在IR上进行优化比在AST上优化更容易,因为IR更接近机器指令,但又不像机器指令那样受限于特定硬件。
-
IR上的优化可以独立于源语言和目标机器,实现“一次优化,到处受益”。
-
-
便于目标代码生成:
-
IR比AST更接近机器指令,因此将其翻译成最终的机器码(或汇编代码)会更加直接和简单。
-
(这里想象一个流程图: C Source -> Front End (Lexical, Syntax, Semantic Analysis) -> AST -> Intermediate Code Generation -> Intermediate Code (IR) -> Code Optimization -> Optimized IR -> Target Code Generation -> Assembly Code -> Assembler -> Object File -> Linker -> Executable File )
中间代码的常见形式:三地址码(Three-Address Code, TAC)
中间代码有很多种形式,比如:
-
后缀式(Postfix Notation):逆波兰表示法,表达式求值方便。
-
三地址码(Three-Address Code, TAC):最常用的一种,每条指令最多包含三个地址(两个操作数,一个结果)。
-
静态单赋值形式(Static Single Assignment, SSA):一种特殊的TAC形式,每个变量只被赋值一次,非常有利于优化。
-
控制流图(Control Flow Graph, CFG):表示程序执行路径的图形结构。
对于我们这个简易编译器,我们选择实现三地址码(TAC)。因为它简单直观,又能很好地体现中间代码的特点。
每条三地址码指令通常是以下形式之一:
-
result = operand1 op operand2(二元操作) -
result = op operand1(一元操作) -
result = operand1(赋值) -
label:(标签) -
goto label(无条件跳转) -
if operand1 goto label(条件跳转,如果operand1为真) -
ifFalse operand1 goto label(条件跳转,如果operand1为假) -
param operand(参数传递) -
call function, num_args(函数调用) -
return operand(函数返回)
举个栗子:
C代码:a = b + c * 2;
对应的三地址码可能如下:
t1 = c * 2 // t1 是一个临时变量
t2 = b + t1 // t2 是一个临时变量
a = t2
看到了吗?每条指令都非常简单,最多三个地址(t1, c, 2)。这种形式非常适合计算机处理。
中间代码的数据结构定义
为了存储生成的三地址码指令,我们需要定义相应的数据结构。
#ifndef INTERMEDIATE_CODE_H
#define INTERMEDIATE_CODE_H
#include <stdlib.h> // For malloc, free
#include <stdio.h> // For printf, fprintf
#include <string.h> // For strdup
#include <stdbool.h> // For bool
// 操作码类型枚举 (三地址码指令的操作)
typedef enum {
// 赋值和声明
IR_ASSIGN, // result = operand1
IR_DECLARE_VAR, // result (声明一个变量,result是变量名)
// 算术运算
IR_ADD, // result = operand1 + operand2
IR_SUB, // result = operand1 - operand2
IR_MUL, // result = operand1 * operand2
IR_DIV, // result = operand1 / operand2
IR_MOD, // result = operand1 % operand2
// 比较运算
IR_EQ, // result = operand1 == operand2
IR_NE, // result = operand1 != operand2
IR_LT, // result = operand1 < operand2
IR_GT, // result = operand1 > operand2
IR_LE, // result = operand1 <= operand2
IR_GE, // result = operand1 >= operand2
// 逻辑运算
IR_AND, // result = operand1 && operand2
IR_OR, // result = operand1 || operand2
IR_NOT, // result = !operand1 (一元逻辑非)
// 位运算
IR_BIT_AND, // result = operand1 & operand2
IR_BIT_OR, // result = operand1 | operand2
IR_BIT_XOR, // result = operand1 ^ operand2
IR_BIT_NOT, // result = ~operand1 (一元位非)
IR_LSHIFT, // result = operand1 << operand2
IR_RSHIFT, // result = operand1 >> operand2
// 一元操作
IR_NEGATE, // result = -operand1 (一元负号)
IR_INCREMENT, // result = ++operand1 (前置自增)
IR_DECREMENT, // result = --operand1 (前置自减)
IR_ADDRESS_OF, // result = &operand1 (取地址)
IR_DEREFERENCE, // result = *operand1 (解引用)
// 控制流
IR_LABEL, // label: (result是标签名)
IR_JUMP, // goto operand1 (operand1是标签名)
IR_JUMPIF_TRUE, // if operand1 goto operand2 (operand1是条件,operand2是标签名)
IR_JUMPIF_FALSE, // ifFalse operand1 goto operand2
// 函数调用和返回
IR_PARAM, // param operand1 (operand1是参数值)
IR_CALL, // result = call operand1, operand2 (operand1是函数名,operand2是参数数量)
IR_RETURN, // return operand1 (operand1是返回值)
// 其他
IR_NOP, // 无操作 (No Operation)
} IROpcode;
// 操作数类型枚举
typedef enum {
OPERAND_NONE, // 无操作数
OPERAND_LITERAL_INT,// 整数常量 (value_int)
OPERAND_LITERAL_STR,// 字符串常量 (value_str)
OPERAND_VAR, // 变量名 (name)
OPERAND_TEMP, // 临时变量名 (name)
OPERAND_LABEL, // 标签名 (name)
} OperandType;
// 操作数结构体
typedef struct Operand {
OperandType type;
union {
int value_int; // 整数常量的值
char *value_str; // 字符串常量的值
char *name; // 变量名、临时变量名、标签名
} data;
} Operand;
// 三地址码指令结构体
typedef struct IRInstruction {
IROpcode opcode; // 操作码
Operand result; // 结果操作数 (左值)
Operand operand1; // 第一个操作数
Operand operand2; // 第二个操作数
// int line; // 对应的源代码行号 (用于调试和错误报告,可选)
} IRInstruction;
// 中间代码列表 (存储所有生成的IR指令)
typedef struct IntermediateCode {
IRInstruction **instructions; // 指令数组
int num_instructions; // 当前指令数量
int capacity; // 数组容量
} IntermediateCode;
// -----------------------------------------------------------------------------
// 操作数和指令创建辅助函数
// -----------------------------------------------------------------------------
// 创建操作数
Operand create_operand_none();
Operand create_operand_int(int value);
Operand create_operand_str(const char *value);
Operand create_operand_var(const char *name);
Operand create_operand_temp(const char *name);
Operand create_operand_label(const char *name);
// 创建IR指令
IRInstruction *create_ir_instruction(IROpcode opcode, Operand result, Operand op1, Operand op2);
// 添加IR指令到中间代码列表
void ir_add_instruction(IntermediateCode *ir_code, IRInstruction *instr);
// -----------------------------------------------------------------------------
// 中间代码列表操作
// -----------------------------------------------------------------------------
// 初始化中间代码列表
IntermediateCode *ir_init();
// 释放中间代码列表内存
void ir_cleanup(IntermediateCode *ir_code);
// 打印中间代码 (用于调试)
void ir_print(IntermediateCode *ir_code);
// 辅助函数:将IROpcode转换为字符串
const char *ir_opcode_to_string(IROpcode opcode);
// 辅助函数:将Operand转换为字符串 (用于打印)
char *operand_to_string(Operand op); // 注意:返回的字符串需要free
#endif // INTERMEDIATE_CODE_H
intermediate_code.c:中间代码数据结构实现
#include "intermediate_code.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// -----------------------------------------------------------------------------
// 操作数创建辅助函数
// -----------------------------------------------------------------------------
Operand create_operand_none() {
Operand op;
op.type = OPERAND_NONE;
memset(&op.data, 0, sizeof(op.data)); // 清零联合体
return op;
}
Operand create_operand_int(int value) {
Operand op;
op.type = OPERAND_LITERAL_INT;
op.data.value_int = value;
return op;
}
Operand create_operand_str(const char *value) {
Operand op;
op.type = OPERAND_LITERAL_STR;
op.data.value_str = strdup(value); // 复制字符串
if (!op.data.value_str) {
fprintf(stderr, "Error: Failed to strdup for string operand.\n");
exit(EXIT_FAILURE);
}
return op;
}
Operand create_operand_var(const char *name) {
Operand op;
op.type = OPERAND_VAR;
op.data.name = strdup(name); // 复制变量名
if (!op.data.name) {
fprintf(stderr, "Error: Failed to strdup for variable operand.\n");
exit(EXIT_FAILURE);
}
return op;
}
Operand create_operand_temp(const char *name) {
Operand op;
op.type = OPERAND_TEMP;
op.data.name = strdup(name); // 复制临时变量名
if (!op.data.name) {
fprintf(stderr, "Error: Failed to strdup for temp operand.\n");
exit(EXIT_FAILURE);
}
return op;
}
Operand create_operand_label(const char *name) {
Operand op;
op.type = OPERAND_LABEL;
op.data.name = strdup(name); // 复制标签名
if (!op.data.name) {
fprintf(stderr, "Error: Failed to strdup for label operand.\n");
exit(EXIT_FAILURE);
}
return op;
}
// -----------------------------------------------------------------------------
// IR指令创建和管理
// -----------------------------------------------------------------------------
IRInstruction *create_ir_instruction(IROpcode opcode, Operand result, Operand op1, Operand op2) {
IRInstruction *instr = (IRInstruction *)malloc(sizeof(IRInstruction));
if (!instr) {
fprintf(stderr, "Error: Failed to allocate memory for IRInstruction.\n");
exit(EXIT_FAILURE);
}
instr->opcode = opcode;
instr->result = result;
instr->operand1 = op1;
instr->operand2 = op2;
return instr;
}
// 添加IR指令到中间代码列表
void ir_add_instruction(IntermediateCode *ir_code, IRInstruction *instr) {
if (ir_code->num_instructions >= ir_code->capacity) {
// 扩容
int new_capacity = ir_code->capacity == 0 ? 32 : ir_code->capacity * 2;
IRInstruction **new_instructions = (IRInstruction **)realloc(ir_code->instructions, new_capacity * sizeof(IRInstruction *));
if (!new_instructions) {
fprintf(stderr, "Error: Failed to reallocate memory for IR instructions.\n");
exit(EXIT_FAILURE);
}
ir_code->instructions = new_instructions;
ir_code->capacity = new_capacity;
}
ir_code->instructions[ir_code->num_instructions++] = instr;
}
// -----------------------------------------------------------------------------
// 中间代码列表操作
// -----------------------------------------------------------------------------
// 初始化中间代码列表
IntermediateCode *ir_init() {
IntermediateCode *ir_code = (IntermediateCode *)malloc(sizeof(IntermediateCode));
if (!ir_code) {
fprintf(stderr, "Error: Failed to allocate memory for IntermediateCode.\n");
exit(EXIT_FAILURE);
}
ir_code->instructions = NULL;
ir_code->num_instructions = 0;
ir_code->capacity = 0;
return ir_code;
}
// 释放操作数内部的动态分配内存
static void free_operand(Operand op) {
if (op.type == OPERAND_LITERAL_STR || op.type == OPERAND_VAR || op.type == OPERAND_TEMP || op.type == OPERAND_LABEL) {
free(op.data.name); // 对于字符串字面量,name字段存储的是value_str
}
}
// 释放中间代码列表内存
void ir_cleanup(IntermediateCode *ir_code) {
if (!ir_code) return;
for (int i = 0; i < ir_code->num_instructions; ++i) {
IRInstruction *instr = ir_code->instructions[i];
if (instr) {
free_operand(instr->result);
free_operand(instr->operand1);
free_operand(instr->operand2);
free(instr); // 释放指令本身
}
}
free(ir_code->instructions); // 释放指令数组
free(ir_code); // 释放IntermediateCode结构体
}
// -----------------------------------------------------------------------------
// 辅助函数:打印中间代码
// -----------------------------------------------------------------------------
// 将IROpcode转换为字符串
const char *ir_opcode_to_string(IROpcode opcode) {
switch (opcode) {
case IR_ASSIGN: return "ASSIGN";
case IR_DECLARE_VAR: return "DECLARE_VAR";
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_EQ: return "EQ";
case IR_NE: return "NE";
case IR_LT: return "LT";
case IR_GT: return "GT";
case IR_LE: return "LE";
case IR_GE: return "GE";
case IR_AND: return "AND";
case IR_OR: return "OR";
case IR_NOT: return "NOT";
case IR_BIT_AND: return "BIT_AND";
case IR_BIT_OR: return "BIT_OR";
case IR_BIT_XOR: return "BIT_XOR";
case IR_BIT_NOT: return "BIT_NOT";
case IR_LSHIFT: return "LSHIFT";
case IR_RSHIFT: return "RSHIFT";
case IR_NEGATE: return "NEGATE";
case IR_INCREMENT: return "INCREMENT";
case IR_DECREMENT: return "DECREMENT";
case IR_ADDRESS_OF: return "ADDRESS_OF";
case IR_DEREFERENCE: return "DEREFERENCE";
case IR_LABEL: return "LABEL";
case IR_JUMP: return "JUMP";
case IR_JUMPIF_TRUE: return "JUMPIF_TRUE";
case IR_JUMPIF_FALSE: return "JUMPIF_FALSE";
case IR_PARAM: return "PARAM";
case IR_CALL: return "CALL";
case IR_RETURN: return "RETURN";
case IR_NOP: return "NOP";
default: return "UNKNOWN_IR_OPCODE";
}
}
// 将Operand转换为字符串 (需要调用者free)
char *operand_to_string(Operand op) {
char *str = NULL;
switch (op.type) {
case OPERAND_NONE:
str = strdup("");
break;
case OPERAND_LITERAL_INT:
// 足够大的缓冲区来存储整数的字符串表示
str = (char *)malloc(32);
if (str) sprintf(str, "%d", op.data.value_int);
break;
case OPERAND_LITERAL_STR:
// 字符串字面量需要加引号
str = (char *)malloc(strlen(op.data.value_str) + 3); // +2 for quotes, +1 for null terminator
if (str) sprintf(str, "\"%s\"", op.data.value_str);
break;
case OPERAND_VAR:
str = strdup(op.data.name);
break;
case OPERAND_TEMP:
// 临时变量通常以 t 开头
str = (char *)malloc(strlen(op.data.name) + 2); // +1 for 't', +1 for null
if (str) sprintf(str, "%s", op.data.name); // 我们的临时变量名已经包含 't'
break;
case OPERAND_LABEL:
// 标签通常以 L 开头
str = (char *)malloc(strlen(op.data.name) + 2); // +1 for 'L', +1 for null
if (str) sprintf(str, "%s", op.data.name); // 我们的标签名已经包含 'L'
break;
default:
str = strdup("?");
break;
}
if (!str) {
fprintf(stderr, "Error: Failed to allocate memory for operand string.\n");
exit(EXIT_FAILURE);
}
return str;
}
// 打印中间代码
void ir_print(IntermediateCode *ir_code) {
if (!ir_code) {
printf("Intermediate code is NULL.\n");
return;
}
printf("--- Intermediate Code (Three-Address Code) ---\n");
for (int i = 0; i < ir_code->num_instructions; ++i) {
IRInstruction *instr = ir_code->instructions[i];
if (!instr) continue;
char *res_str = operand_to_string(instr->result);
char *op1_str = operand_to_string(instr->operand1);
char *op2_str = operand_to_string(instr->operand2);
printf("%4d: ", i); // 打印指令序号
switch (instr->opcode) {
case IR_LABEL:
printf("%s:\n", res_str); // 标签单独一行
break;
case IR_JUMP:
printf("goto %s\n", op1_str);
break;
case IR_JUMPIF_TRUE:
printf("if %s goto %s\n", op1_str, op2_str);
break;
case IR_JUMPIF_FALSE:
printf("ifFalse %s goto %s\n", op1_str, op2_str);
break;
case IR_PARAM:
printf("param %s\n", op1_str);
break;
case IR_CALL:
printf("%s = call %s, %s\n", res_str, op1_str, op2_str); // op2_str是参数数量
break;
case IR_RETURN:
printf("return %s\n", op1_str);
break;
case IR_ASSIGN:
printf("%s = %s\n", res_str, op1_str);
break;
case IR_DECLARE_VAR:
printf("DECLARE_VAR %s\n", res_str); // 声明变量
break;
case IR_NEGATE:
case IR_NOT:
case IR_BIT_NOT:
case IR_INCREMENT:
case IR_DECREMENT:
case IR_ADDRESS_OF:
case IR_DEREFERENCE:
printf("%s = %s %s\n", res_str, ir_opcode_to_string(instr->opcode), op1_str);
break;
case IR_NOP:
printf("NOP\n");
break;
default: // 二元操作
printf("%s = %s %s %s\n", res_str, op1_str, ir_opcode_to_string(instr->opcode), op2_str);
break;
}
free(res_str);
free(op1_str);
free(op2_str);
}
printf("--------------------------------------------\n");
}
中间代码生成器实现:ir_generator.h 和 ir_generator.c
现在,咱们开始编写中间代码生成器的核心逻辑。它将递归遍历AST,并为每个AST节点生成相应的IR指令。
ir_generator.h:中间代码生成器接口定义
#ifndef IR_GENERATOR_H
#define IR_GENERATOR_H
#include "ast.h" // 需要AST节点定义
#include "intermediate_code.h" // 需要IR指令定义
// 中间代码生成器的入口函数
// 参数:program_ast - 语义分析器生成的AST根节点
// 返回值:指向生成的中间代码列表的指针
IntermediateCode *generate_intermediate_code(ASTNode *program_ast);
#endif // IR_GENERATOR_H
ir_generator.c:中间代码生成器实现
#include "ir_generator.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 全局变量,用于生成唯一的临时变量和标签名
static int temp_counter_g = 0;
static int label_counter_g = 0;
// 全局中间代码列表
static IntermediateCode *current_ir_code_g = NULL;
// -----------------------------------------------------------------------------
// 辅助函数:生成临时变量和标签
// -----------------------------------------------------------------------------
// 生成一个新的临时变量名 (例如 t0, t1, t2...)
static char *new_temp() {
char *temp_name = (char *)malloc(16); // 足够存储 "t" + 整数 + '\0'
if (!temp_name) {
fprintf(stderr, "Error: Failed to allocate memory for new temp name.\n");
exit(EXIT_FAILURE);
}
sprintf(temp_name, "t%d", temp_counter_g++);
return temp_name;
}
// 生成一个新的标签名 (例如 L0, L1, L2...)
static char *new_label() {
char *label_name = (char *)malloc(16); // 足够存储 "L" + 整数 + '\0'
if (!label_name) {
fprintf(stderr, "Error: Failed to allocate memory for new label name.\n");
exit(EXIT_FAILURE);
}
sprintf(label_name, "L%d", label_counter_g++);
return label_name;
}
// -----------------------------------------------------------------------------
// IR指令发射辅助函数
// -----------------------------------------------------------------------------
// 发射一条三地址码指令到全局中间代码列表中
static void emit_ir(IROpcode opcode, Operand result, Operand op1, Operand op2) {
IRInstruction *instr = create_ir_instruction(opcode, result, op1, op2);
ir_add_instruction(current_ir_code_g, instr);
}
// -----------------------------------------------------------------------------
// AST节点到IR的翻译函数 (递归遍历AST)
// -----------------------------------------------------------------------------
// 前向声明所有翻译函数
static void generate_ir_function_declaration(ASTNode *node);
static void generate_ir_compound_statement(ASTNode *node);
static void generate_ir_statement(ASTNode *node);
static void generate_ir_variable_declaration(ASTNode *node);
static void generate_ir_return_statement(ASTNode *node);
static void generate_ir_if_statement(ASTNode *node);
static void generate_ir_while_statement(ASTNode *node);
static void generate_ir_expression_statement(ASTNode *node);
static Operand generate_ir_expression(ASTNode *node); // 表达式翻译返回其结果的Operand
// 翻译函数声明
static void generate_ir_function_declaration(ASTNode *node) {
if (node->type != AST_FUNCTION_DECLARATION) {
fprintf(stderr, "IRGen Error: Expected AST_FUNCTION_DECLARATION node.\n");
exit(EXIT_FAILURE);
}
ASTFunctionDeclaration *func_decl = node->data.func_decl;
// 发射函数入口标签
char *func_label = strdup(func_decl->name); // 函数名作为标签
emit_ir(IR_LABEL, create_operand_label(func_label), create_operand_none(), create_operand_none());
free(func_label); // strdup的内存需要释放
// 翻译函数体
generate_ir_compound_statement(func_decl->body);
// TODO: 考虑隐式返回,如果函数返回void且没有return语句,或者返回int但没有return
// 实际编译器会在这里添加一个隐式的 return 或确保所有路径都有return
// 对于void函数,可以在这里添加一个 IR_RETURN_VOID 或 IR_NOP
if (strcmp(func_decl->return_type, "void") == 0) {
// 对于void函数,确保函数末尾有一个返回指令
// 这里简化为添加一个 NOP 或一个无值的 RETURN
emit_ir(IR_RETURN, create_operand_none(), create_operand_none(), create_operand_none());
}
}
// 翻译复合语句 (代码块)
static void generate_ir_compound_statement(ASTNode *node) {
if (node->type != AST_COMPOUND_STATEMENT) {
fprintf(stderr, "IRGen Error: Expected AST_COMPOUND_STATEMENT node.\n");
exit(EXIT_FAILURE);
}
ASTCompoundStatement *compound_stmt = node->data.compound_stmt;
// 遍历并翻译所有语句
for (int i = 0; i < compound_stmt->num_statements; ++i) {
generate_ir_statement(compound_stmt->statements[i]);
}
}
// 翻译语句 (分发函数)
static void generate_ir_statement(ASTNode *node) {
if (!node) return; // 空语句或无效节点
switch (node->type) {
case AST_VARIABLE_DECLARATION:
generate_ir_variable_declaration(node);
break;
case AST_RETURN_STATEMENT:
generate_ir_return_statement(node);
break;
case AST_IF_STATEMENT:
generate_ir_if_statement(node);
break;
case AST_WHILE_STATEMENT:
generate_ir_while_statement(node);
break;
case AST_EXPRESSION_STATEMENT:
generate_ir_expression_statement(node);
break;
case AST_COMPOUND_STATEMENT:
generate_ir_compound_statement(node); // 复合语句可以作为子语句
break;
default:
fprintf(stderr, "IRGen Error: Unhandled statement type %s.\n", ast_node_type_to_string(node->type));
exit(EXIT_FAILURE);
}
}
// 翻译变量声明
static void generate_ir_variable_declaration(ASTNode *node) {
if (node->type != AST_VARIABLE_DECLARATION) {
fprintf(stderr, "IRGen Error: Expected AST_VARIABLE_DECLARATION node.\n");
exit(EXIT_FAILURE);
}
ASTVariableDeclaration *var_decl = node->data.var_decl;
// 发射一个声明指令,用于后续的内存分配或符号表偏移量计算
emit_ir(IR_DECLARE_VAR, create_operand_var(var_decl->name), create_operand_none(), create_operand_none());
// 如果有初始化表达式,生成赋值指令
if (var_decl->initializer) {
Operand initializer_val = generate_ir_expression(var_decl->initializer);
emit_ir(IR_ASSIGN, create_operand_var(var_decl->name), initializer_val, create_operand_none());
free_operand(initializer_val); // 释放临时操作数内存
}
}
// 翻译返回语句
static void generate_ir_return_statement(ASTNode *node) {
if (node->type != AST_RETURN_STATEMENT) {
fprintf(stderr, "IRGen Error: Expected AST_RETURN_STATEMENT node.\n");
exit(EXIT_FAILURE);
}
ASTReturnStatement *return_stmt = node->data.return_stmt;
if (return_stmt->expression) {
Operand return_val = generate_ir_expression(return_stmt->expression);
emit_ir(IR_RETURN, create_operand_none(), return_val, create_operand_none());
free_operand(return_val); // 释放临时操作数内存
} else {
// 无返回值的 return (for void functions)
emit_ir(IR_RETURN, create_operand_none(), create_operand_none(), create_operand_none());
}
}
// 翻译 if 语句
// if (condition) then_statement else else_statement
// IR 结构:
// evaluate condition to temp_cond
// ifFalse temp_cond goto L_else
// generate_ir_statement(then_statement)
// goto L_end_if
// L_else:
// generate_ir_statement(else_statement) (if exists)
// L_end_if:
static void generate_ir_if_statement(ASTNode *node) {
if (node->type != AST_IF_STATEMENT) {
fprintf(stderr, "IRGen Error: Expected AST_IF_STATEMENT node.\n");
exit(EXIT_FAILURE);
}
ASTIfStatement *if_stmt = node->data.if_stmt;
Operand cond_result = generate_ir_expression(if_stmt->condition); // 翻译条件表达式
char *label_else = new_label();
char *label_end_if = new_label();
// 如果条件为假,则跳转到else分支或if语句结束
emit_ir(IR_JUMPIF_FALSE, create_operand_none(), cond_result, create_operand_label(label_else));
free_operand(cond_result); // 释放临时操作数内存
// 翻译 then 分支
generate_ir_statement(if_stmt->then_statement);
// 如果有else分支,则在then分支结束后无条件跳转到if语句结束
if (if_stmt->else_statement) {
emit_ir(IR_JUMP, create_operand_none(), create_operand_label(label_end_if), create_operand_none());
}
// else 分支的标签
emit_ir(IR_LABEL, create_operand_label(label_else), create_operand_none(), create_operand_none());
free(label_else); // 释放标签名内存
// 翻译 else 分支 (如果存在)
if (if_stmt->else_statement) {
generate_ir_statement(if_stmt->else_statement);
}
// if 语句结束的标签
emit_ir(IR_LABEL, create_operand_label(label_end_if), create_operand_none(), create_operand_none());
free(label_end_if); // 释放标签名内存
}
// 翻译 while 语句
// while (condition) body_statement
// IR 结构:
// L_loop_start:
// evaluate condition to temp_cond
// ifFalse temp_cond goto L_loop_end
// generate_ir_statement(body_statement)
// goto L_loop_start
// L_loop_end:
static void generate_ir_while_statement(ASTNode *node) {
if (node->type != AST_WHILE_STATEMENT) {
fprintf(stderr, "IRGen Error: Expected AST_WHILE_STATEMENT node.\n");
exit(EXIT_FAILURE);
}
ASTWhileStatement *while_stmt = node->data.while_stmt;
char *label_loop_start = new_label();
char *label_loop_end = new_label();
// 循环开始标签
emit_ir(IR_LABEL, create_operand_label(label_loop_start), create_operand_none(), create_operand_none());
Operand cond_result = generate_ir_expression(while_stmt->condition); // 翻译条件表达式
// 如果条件为假,则跳转到循环结束
emit_ir(IR_JUMPIF_FALSE, create_operand_none(), cond_result, create_operand_label(label_loop_end));
free_operand(cond_result); // 释放临时操作数内存
// 翻译循环体
generate_ir_statement(while_stmt->body);
// 无条件跳转回循环开始
emit_ir(IR_JUMP, create_operand_none(), create_operand_label(label_loop_start), create_operand_none());
// 循环结束标签
emit_ir(IR_LABEL, create_operand_label(label_loop_end), create_operand_none(), create_operand_none());
free(label_loop_start); // 释放标签名内存
free(label_loop_end); // 释放标签名内存
}
// 翻译表达式语句
static void generate_ir_expression_statement(ASTNode *node) {
if (node->type != AST_EXPRESSION_STATEMENT) {
fprintf(stderr, "IRGen Error: Expected AST_EXPRESSION_STATEMENT node.\n");
exit(EXIT_FAILURE);
}
ASTExpressionStatement *expr_stmt = node->data.expr_stmt;
if (expr_stmt->expression) {
// 翻译表达式,其结果通常会被丢弃,但可能包含副作用(如赋值、自增)
Operand result_op = generate_ir_expression(expr_stmt->expression);
free_operand(result_op); // 释放临时操作数内存
} else {
// 空表达式语句,生成一个 NOP
emit_ir(IR_NOP, create_operand_none(), create_operand_none(), create_operand_none());
}
}
// 翻译表达式,并返回其结果的Operand
static Operand generate_ir_expression(ASTNode *node) {
if (!node) {
fprintf(stderr, "IRGen Error: Null expression node encountered.\n");
exit(EXIT_FAILURE);
}
switch (node->type) {
case AST_INT_LITERAL: {
// 整数常量直接作为操作数
return create_operand_int(node->data.int_literal->value);
}
case AST_STRING_LITERAL: {
// 字符串常量作为操作数
return create_operand_str(node->data.string_literal->value);
}
case AST_IDENTIFIER: {
// 标识符(变量)直接作为操作数
return create_operand_var(node->data.identifier->name);
}
case AST_BINARY_EXPRESSION: {
ASTBinaryExpression *binary_expr = node->data.binary_expr;
Operand left_op = generate_ir_expression(binary_expr->left); // 递归翻译左操作数
Operand right_op = generate_ir_expression(binary_expr->right); // 递归翻译右操作数
char *temp_res_name = new_temp(); // 为结果生成一个新的临时变量
Operand result_op = create_operand_temp(temp_res_name);
free(temp_res_name); // 临时变量名已复制到Operand中,可以释放
IROpcode opcode;
switch (binary_expr->op) {
case OP_ADD: opcode = IR_ADD; break;
case OP_SUB: opcode = IR_SUB; break;
case OP_MUL: opcode = IR_MUL; break;
case OP_DIV: opcode = IR_DIV; break;
case OP_MOD: opcode = IR_MOD; break;
case OP_EQ: opcode = IR_EQ; break;
case OP_NE: opcode = IR_NE; break;
case OP_LT: opcode = IR_LT; break;
case OP_GT: opcode = IR_GT; break;
case OP_LE: opcode = IR_LE; break;
case OP_GE: opcode = IR_GE; break;
case OP_AND: opcode = IR_AND; break;
case OP_OR: opcode = IR_OR; break;
case OP_BIT_AND: opcode = IR_BIT_AND; break;
case OP_BIT_OR: opcode = IR_BIT_OR; break;
case OP_BIT_XOR: opcode = IR_BIT_XOR; break;
case OP_LSHIFT: opcode = IR_LSHIFT; break;
case OP_RSHIFT: opcode = IR_RSHIFT; break;
default:
fprintf(stderr, "IRGen Error: Unhandled binary operator %s.\n", operator_type_to_string(binary_expr->op));
exit(EXIT_FAILURE);
}
emit_ir(opcode, result_op, left_op, right_op); // 发射IR指令
free_operand(left_op); // 释放临时操作数内存
free_operand(right_op); // 释放临时操作数内存
return result_op; // 返回结果操作数
}
case AST_UNARY_EXPRESSION: {
ASTUnaryExpression *unary_expr = node->data.unary_expr;
Operand operand_val = generate_ir_expression(unary_expr->operand); // 递归翻译操作数
char *temp_res_name = new_temp();
Operand result_op = create_operand_temp(temp_res_name);
free(temp_res_name);
IROpcode opcode;
switch (unary_expr->op) {
case OP_NEGATE: opcode = IR_NEGATE; break;
case OP_NOT: opcode = IR_NOT; break;
case OP_BIT_NOT: opcode = IR_BIT_NOT; break;
case OP_INCREMENT: opcode = IR_INCREMENT; break; // 前置自增
case OP_DECREMENT: opcode = IR_DECREMENT; break; // 前置自减
case OP_ADDRESS_OF: opcode = IR_ADDRESS_OF; break;
case OP_DEREFERENCE: opcode = IR_DEREFERENCE; break;
default:
fprintf(stderr, "IRGen Error: Unhandled unary operator %s.\n", operator_type_to_string(unary_expr->op));
exit(EXIT_FAILURE);
}
emit_ir(opcode, result_op, operand_val, create_operand_none());
free_operand(operand_val);
return result_op;
}
case AST_ASSIGNMENT_EXPRESSION: {
ASTAssignmentExpression *assign_expr = node->data.assign_expr;
Operand left_op = generate_ir_expression(assign_expr->left); // 左侧操作数 (通常是变量)
Operand right_op = generate_ir_expression(assign_expr->right); // 右侧表达式的值
IROpcode opcode;
// 对于复合赋值,需要先计算,再赋值
switch (assign_expr->op) {
case OP_ASSIGN: opcode = IR_ASSIGN; break;
case OP_ADD_ASSIGN: opcode = IR_ADD; break; // result = left + right
case OP_SUB_ASSIGN: opcode = IR_SUB; break;
case OP_MUL_ASSIGN: opcode = IR_MUL; break;
case OP_DIV_ASSIGN: opcode = IR_DIV; break;
case OP_MOD_ASSIGN: opcode = IR_MOD; break;
case OP_LSHIFT_ASSIGN: opcode = IR_LSHIFT; break;
case OP_RSHIFT_ASSIGN: opcode = IR_RSHIFT; break;
case OP_BIT_AND_ASSIGN: opcode = IR_BIT_AND; break;
case OP_BIT_OR_ASSIGN: opcode = IR_BIT_OR; break;
case OP_BIT_XOR_ASSIGN: opcode = IR_BIT_XOR; break;
default:
fprintf(stderr, "IRGen Error: Unhandled assignment operator %s.\n", operator_type_to_string(assign_expr->op));
exit(EXIT_FAILURE);
}
if (assign_expr->op == OP_ASSIGN) {
// 简单赋值:result = right_op
emit_ir(opcode, left_op, right_op, create_operand_none());
} else {
// 复合赋值:result = left_op op right_op
// 需要一个临时变量来存储运算结果
char *temp_res_name = new_temp();
Operand temp_result_op = create_operand_temp(temp_res_name);
free(temp_res_name);
emit_ir(opcode, temp_result_op, left_op, right_op); // 先计算
emit_ir(IR_ASSIGN, left_op, temp_result_op, create_operand_none()); // 再赋值给左侧变量
free_operand(temp_result_op);
}
// 赋值表达式的结果是左侧变量的值
// 这里需要创建一个新的操作数来表示赋值后的值,因为left_op可能已经被emit_ir“消费”了
Operand final_result_op = create_operand_var(left_op.data.name); // 假设左侧是变量
free_operand(left_op); // 释放临时操作数内存
free_operand(right_op); // 释放临时操作数内存
return final_result_op; // 返回赋值后的结果
}
// TODO: AST_CALL_EXPRESSION (函数调用)
// TODO: AST_ARRAY_ACCESS (数组访问)
// TODO: AST_MEMBER_ACCESS (结构体成员访问)
default:
fprintf(stderr, "IRGen Error: Unhandled expression type %s.\n", ast_node_type_to_string(node->type));
exit(EXIT_FAILURE);
}
}
// -----------------------------------------------------------------------------
// 中间代码生成器入口函数
// -----------------------------------------------------------------------------
IntermediateCode *generate_intermediate_code(ASTNode *program_ast) {
if (program_ast->type != AST_PROGRAM) {
fprintf(stderr, "IRGen Error: Expected AST_PROGRAM as root node for IR generation.\n");
exit(EXIT_FAILURE);
}
// 初始化全局中间代码列表
current_ir_code_g = ir_init();
temp_counter_g = 0;
label_counter_g = 0;
ASTProgram *program = program_ast->data.program;
// 遍历所有函数声明并生成IR
for (int i = 0; i < program->num_function_declarations; ++i) {
generate_ir_function_declaration(program->function_declarations[i]);
}
return current_ir_code_g;
}
main.c:更新以集成中间代码生成器
现在,咱们更新 main.c,在语义分析之后,调用中间代码生成器,并打印生成的IR。
#include "lexer.h"
#include "parser.h"
#include "ast.h"
#include "semantic_analyzer.h"
#include "intermediate_code.h" // 包含中间代码头文件
#include "ir_generator.h" // 包含中间代码生成器头文件
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <source_file.c>\n", argv[0]);
return EXIT_FAILURE;
}
const char *source_file_path = argv[1];
// 1. 初始化词法分析器
if (!lexer_init(source_file_path)) {
fprintf(stderr, "Failed to initialize lexer for file: %s\n", source_file_path);
return EXIT_FAILURE;
}
// 2. 初始化语法分析器
parser_init();
// 3. 开始解析整个程序,构建AST
printf("\n--- Starting Syntax Analysis ---\n");
ASTNode *program_ast = parse_program();
printf("--- Syntax Analysis Completed ---\n");
// 4. 打印生成的AST (用于调试和验证)
printf("\n--- Generated Abstract Syntax Tree (AST) (After Parsing) ---\n");
print_ast(program_ast, 0);
printf("--------------------------------------------\n");
// 5. 开始语义分析
printf("\n--- Starting Semantic Analysis ---\n");
if (semantic_analyze_program(program_ast)) {
printf("--- Semantic Analysis Completed Successfully! ---\n");
} else {
fprintf(stderr, "--- Semantic Analysis Failed! ---\n");
// 语义分析失败,清理资源并退出
free_ast_node(program_ast);
lexer_cleanup();
parser_cleanup();
return EXIT_FAILURE;
}
// 6. 打印经过语义分析修饰后的AST (可选,但有助于调试)
printf("\n--- Generated Abstract Syntax Tree (AST) (After Semantic Analysis) ---\n");
print_ast(program_ast, 0); // 此时AST节点应该有data_type信息了
printf("--------------------------------------------\n");
// 7. 开始中间代码生成
printf("\n--- Starting Intermediate Code Generation ---\n");
IntermediateCode *ir_code = generate_intermediate_code(program_ast);
printf("--- Intermediate Code Generation Completed ---\n");
// 8. 打印生成的中间代码
ir_print(ir_code);
// 9. 清理所有资源
printf("\n--- Cleaning up all resources ---\n");
free_ast_node(program_ast); // 释放AST内存
ir_cleanup(ir_code); // 释放中间代码内存
lexer_cleanup(); // 清理词法分析器
parser_cleanup(); // 清理解析器
printf("\nCompilation phase (Lexical, Syntax, Semantic Analysis, IR Generation) completed successfully.\n");
return EXIT_SUCCESS;
}
示例C语言源代码文件:test.c (更新,以生成更丰富的IR)
为了更好地展示中间代码生成,咱们更新 test.c,包含更多表达式和控制流:
// test.c
int global_var = 10; // 全局变量,目前只声明,不生成IR操作
int calculate(int x, int y) { // 参数处理简化
int a = x + y;
int b = a * 2;
if (a > 10) {
b = b - 5;
} else {
b = b + 3;
}
while (b < 20) {
b = b + 1;
}
return b;
}
void main() {
int val1 = 5;
int val2 = 7;
int result;
result = calculate(val1, val2); // 函数调用简化处理,只生成call指令
// 复杂的表达式
int complex_expr = (val1 + 3) * (val2 - 1) / 2;
if (complex_expr == 24 && val1 != 0) {
result = result + 100;
} else {
result = result - 50;
}
val1++; // 自增
val2--; // 自减
// 字符串字面量
char *message = "Hello IR!"; // 字符串字面量赋值
// 另一个函数调用(简化)
// printf("Result: %d\n", result); // 假设printf是外部函数
return;
}
重要说明:
-
函数参数和调用:目前
generate_ir_function_declaration和generate_ir_expression中的函数调用处理非常简化。calculate(val1, val2)这样的调用,我们只生成IR_CALL指令,但没有处理参数传递(IR_PARAM指令)和返回值接收的完整逻辑。这需要更复杂的运行时栈帧管理,超出本阶段的简化目标。 -
全局变量:
global_var目前只在语义分析阶段被识别并添加到符号表,但我们没有为其生成特定的IR指令来分配全局内存,这通常在目标代码生成阶段处理。 -
字符串字面量:
char *message = "Hello IR!";会生成IR_DECLARE_VAR message和IR_ASSIGN message, "Hello IR!"。字符串字面量的实际存储和地址分配通常在数据段进行,并在目标代码生成阶段处理。 -
自增/自减:
val1++;会被翻译成IR_INCREMENT temp, val1和IR_ASSIGN val1, temp这种形式,或者直接IR_INCREMENT val1, val1(如果指令集支持)。我们这里简化为IR_INCREMENT result, operand。
编译和运行
-
保存文件:将上述代码块分别保存为
ast.h,ast.c,symbol_table.h,symbol_table.c,semantic_analyzer.h,semantic_analyzer.c,intermediate_code.h,intermediate_code.c,ir_generator.h,ir_generator.c和test.c。确保所有文件都是最新版本。 -
编译:在终端中使用GCC编译:
gcc -o mycompiler main.c lexer.c parser.c ast.c symbol_table.c semantic_analyzer.c intermediate_code.c ir_generator.c -Wall -Wextra -std=c99 -
运行:
./mycompiler test.c
你将看到程序输出词法分析的Token流、语法分析器构建的AST、语义分析后的AST(带有类型标注),以及最终由中间代码生成器产生的三地址码(IR)指令序列!
原理分析与逻辑剖析:中间代码生成器是如何“绘制蓝图”的?
咱们的中间代码生成器,其核心任务就是将AST这个“高层思想”转化为三地址码这个“低层蓝图”。它通过递归遍历AST,并根据每个AST节点的类型,发出(emit)相应的IR指令。
1. 临时变量和标签的生成
-
new_temp():在表达式求值过程中,为了存储中间结果,我们需要引入临时变量(Temporary Variables)。这些临时变量在源代码中并不存在,它们是编译器为了方便计算而创建的。例如a = b + c * 2;中的t1和t2。new_temp()函数负责生成唯一的临时变量名,确保它们不会冲突。 -
new_label():在处理控制流语句(如if、while)时,我们需要**标签(Labels)**来标记代码的跳转目标。new_label()函数负责生成唯一的标签名。
2. emit_ir() 函数:IR指令的“打印机”
emit_ir(opcode, result, op1, op2) 函数是中间代码生成的核心。每当翻译器决定生成一条IR指令时,它就调用 emit_ir。这个函数会:
-
创建一个新的
IRInstruction结构体。 -
填充其
opcode、result、operand1、operand2字段。 -
将这条指令添加到全局的
current_ir_code_g列表中。
3. AST遍历与IR翻译的结合
中间代码生成器通过递归函数遍历AST,并在每个节点上执行翻译操作:
-
generate_intermediate_code():入口函数,初始化IR列表和计数器,然后遍历AST的根节点(AST_PROGRAM),调用generate_ir_function_declaration()翻译每个函数。 -
generate_ir_function_declaration():-
为函数生成一个入口标签(
IR_LABEL),标签名就是函数名。 -
递归调用
generate_ir_compound_statement()翻译函数体。 -
对于
void函数,在末尾添加一个隐式的IR_RETURN指令。
-
-
generate_ir_compound_statement():-
遍历复合语句(代码块)中的所有语句,并递归调用
generate_ir_statement()翻译它们。
-
-
generate_ir_statement():这是一个分发函数,根据AST节点的类型,调用相应的语句翻译函数。 -
generate_ir_variable_declaration():-
生成
IR_DECLARE_VAR指令,告知后端需要为这个变量分配空间。 -
如果变量有初始化表达式,则递归调用
generate_ir_expression()翻译初始化表达式,并生成一个IR_ASSIGN指令,将表达式结果赋值给变量。
-
-
generate_ir_expression():这是翻译表达式的核心。它会递归地翻译表达式的子节点,并返回一个Operand,代表该表达式的计算结果(可能是字面量、变量或临时变量)。-
字面量(
AST_INT_LITERAL,AST_STRING_LITERAL):直接创建相应的OPERAND_LITERAL_INT或OPERAND_LITERAL_STR操作数。 -
标识符(
AST_IDENTIFIER):创建OPERAND_VAR操作数。 -
二元表达式(
AST_BINARY_EXPRESSION):-
递归翻译左右子表达式,得到它们的
Operand。 -
生成一个新的临时变量
t_new作为结果。 -
根据操作符(
+,-,*,/,==,&&等),发射一条对应的三地址码指令,例如t_new = op1_result OP op2_result。 -
返回
t_new的Operand。
-
-
一元表达式(
AST_UNARY_EXPRESSION):-
递归翻译操作数,得到其
Operand。 -
生成一个新的临时变量
t_new作为结果。 -
根据操作符(
-,!,++等),发射一条对应的三地址码指令,例如t_new = OP op_result。 -
返回
t_new的Operand。
-
-
赋值表达式(
AST_ASSIGNMENT_EXPRESSION):-
递归翻译左侧(目标变量)和右侧(值)表达式。
-
对于简单赋值
=,发射IR_ASSIGN target, value。 -
对于复合赋值
+=,-=等,需要先生成一个二元运算指令(例如IR_ADD temp, target, value),再生成一个赋值指令(IR_ASSIGN target, temp)。 -
返回赋值结果的
Operand(即左侧变量的Operand)。
-
-
-
generate_ir_return_statement():-
如果
return语句有表达式,则翻译表达式,并生成IR_RETURN result_op。 -
如果没有表达式,则生成
IR_RETURN(无操作数)。
-
-
generate_ir_if_statement():-
翻译条件表达式,得到结果
temp_cond。 -
生成两个新标签:
L_else和L_end_if。 -
发射
IR_JUMPIF_FALSE temp_cond, L_else。 -
翻译
then分支语句。 -
如果存在
else分支,发射IR_JUMP L_end_if。 -
发射
IR_LABEL L_else。 -
翻译
else分支语句(如果存在)。 -
发射
IR_LABEL L_end_if。
-
-
generate_ir_while_statement():-
生成两个新标签:
L_loop_start和L_loop_end。 -
发射
IR_LABEL L_loop_start。 -
翻译条件表达式,得到结果
temp_cond。 -
发射
IR_JUMPIF_FALSE temp_cond, L_loop_end。 -
翻译循环体语句。
-
发射
IR_JUMP L_loop_start。 -
发射
IR_LABEL L_loop_end。
-
中间代码生成器工作流程(思维导图概念)
(这里想象一个流程图,描述 generate_intermediate_code() 的主要步骤:
开始 -> ir_init() (初始化IR列表) -> temp_counter_g = 0, label_counter_g = 0 -> 遍历 AST_PROGRAM 的 function_declarations 列表 -> 对于每个 AST_FUNCTION_DECLARATION 节点: generate_ir_function_declaration(func_node) -> emit_ir(IR_LABEL, func_name_label) -> generate_ir_compound_statement(func_body_node) -> 遍历 AST_COMPOUND_STATEMENT 的 statements 列表 -> 对于每个 AST_STATEMENT 节点: generate_ir_statement(stmt_node) -> (根据类型分发到) generate_ir_variable_declaration() -> emit_ir(IR_DECLARE_VAR), emit_ir(IR_ASSIGN) generate_ir_return_statement() -> emit_ir(IR_RETURN) generate_ir_if_statement() -> emit_ir(IR_JUMPIF_FALSE), emit_ir(IR_JUMP), emit_ir(IR_LABEL) generate_ir_while_statement() -> emit_ir(IR_LABEL), emit_ir(IR_JUMPIF_FALSE), emit_ir(IR_JUMP) generate_ir_expression_statement() -> generate_ir_expression() generate_ir_expression(expr_node) -> (递归处理子表达式) -> AST_INT_LITERAL / AST_STRING_LITERAL / AST_IDENTIFIER: 返回对应Operand -> AST_BINARY_EXPRESSION: generate_ir_expression(left), generate_ir_expression(right), new_temp(), emit_ir(IR_OP, temp, left_op, right_op), 返回temp -> AST_UNARY_EXPRESSION: generate_ir_expression(operand), new_temp(), emit_ir(IR_UNARY_OP, temp, operand_op), 返回temp -> AST_ASSIGNMENT_EXPRESSION: generate_ir_expression(left), generate_ir_expression(right), emit_ir(IR_ASSIGN/IR_COMPOUND_ASSIGN_SEQ), 返回left_op 返回 current_ir_code_g -> 结束 )
这个流程图详细展示了中间代码生成器如何遍历AST,并根据不同节点类型生成相应的IR指令,包括临时变量和标签的引入,以及控制流语句的翻译。
总结与展望:你已经拥有了代码的“蓝图”!
恭喜你,老铁!你已经成功地给你的C语言编译器装上了“蓝图绘制师”——中间代码生成器!
你现在应该明白:
-
中间代码是编译器连接高层源代码(AST)和低层机器码的重要桥梁。
-
它简化了编译器结构,方便了代码优化,并使得目标代码生成更加直接。
-
我们实现了**三地址码(TAC)**作为中间表示,每条指令都简单明了,最多包含三个地址。
-
中间代码生成器通过递归遍历AST,为每个AST节点生成对应的IR指令,并引入了临时变量和标签来处理表达式求值和控制流。
从字符流到Token流,从Token流到AST,从AST到带有语义信息的AST,再从AST到中间代码,你已经一步步深入了C语言编译器的“心脏”!你现在不仅仅是会写C代码,更是能从编译器的角度去“看透”C代码的“执行蓝图”了!是不是感觉对C语言的理解又提升了一个巨大的维度?
下一篇文章,我们将进入编译器的第五阶段,也是最激动人心的阶段——代码优化与目标代码生成!我们将学习如何对这份“蓝图”(中间代码)进行“精装修”(优化),让它跑得更快、更省资源,然后把它“打印”成CPU能直接执行的汇编代码,最终生成可执行文件!
敬请期待!如果你觉得这篇文章让你彻底搞懂了中间代码生成,请务必点赞、收藏、转发,让更多想彻底搞懂C语言底层原理的兄弟们看到!你的支持是我继续创作的最大动力!
-------------------------------------------------------------------------------------------------更新于2025.6.24 下午4点18

8071

被折叠的 条评论
为什么被折叠?



