95%大厂程序员没看透的底层原理:彻底搞懂 C 编译器(中)从手撸语义分析到中间代码生成 彻底掀开C语言的“底裤”:手撸编译器系列 (中)

写在最开头:

 

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

手撸编译器系列(三)—— 语义分析

引言:从“骨架”到“血肉”——代码的“意义”何在?

兄弟们,回顾一下咱们的“手撸编译器”之旅:

  1. 词法分析(Lexical Analysis):把C代码的字符流,分解成一个个有意义的“词法单元”(Token)。这是编译器的“眼睛”,能识别出 intmain123 这些“单词”。

  2. 语法分析(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)**决定的。

  • 全局作用域:在所有函数之外声明的变量,在整个程序中都可见。

  • 函数作用域:函数内部声明的参数和局部变量,只在该函数内部可见。

  • 块作用域:在 ifwhilefor 语句的代码块 {} 内声明的变量,只在该代码块内可见。

语义分析器需要跟踪当前所处的作用域,以便正确地解析标识符(变量名、函数名等)。当你在代码中引用一个变量时,语义分析器会从当前作用域开始,逐级向外查找,直到找到该变量的声明。如果找不到,就报告“未声明标识符”错误。

2. 类型检查(Type Checking):代码的“血型”匹配

C语言是强类型语言,这意味着每个变量和表达式都有一个确定的类型。语义分析器会检查:

  • 赋值兼容性int a; a = 10; 合法,int a; a = "hello"; 不合法。

  • 操作符类型匹配a + b,如果 ab 都是整数,结果是整数;如果一个是整数一个是字符串,那通常是错误。

  • 函数调用类型匹配:函数参数的类型和数量是否与函数定义匹配。

  • 返回类型匹配return 语句中的表达式类型是否与函数声明的返回类型匹配。

3. 声明与定义检查:名字的“合法性”

  • 重复定义:同一个作用域内,不能声明两个同名的变量或函数。

  • 使用前声明:所有使用的标识符都必须在使用前声明。

  • 函数定义检查:确保函数在被调用时已经被定义(或者至少有声明)。

4. 其他语义检查(根据语言特性)

  • 控制流检查:例如,breakcontinue 语句是否在循环或 switch 语句内部。

  • 可达性检查:例如,函数体中 return 语句后的代码是否可达。

  • 左值检查:赋值操作符的左侧必须是可修改的左值(例如,10 = a; 是非法的)。

符号表(Symbol Table):编译器的“记忆力”

要实现上述语义检查,语义分析器必须有一个强大的“记忆力”,能够记住所有声明过的标识符及其属性。这个“记忆力”的核心就是符号表(Symbol Table)

什么是符号表?

符号表是一个数据结构,用于存储源代码中所有标识符(Identifier)的信息。每个标识符在符号表中都对应一个符号项(Symbol Entry),其中包含:

  • 名称(Name):标识符的字符串名称(如 my_variable)。

  • 类型(Type):标识符的类型(如 intvoid、函数类型等)。

  • 作用域(Scope):标识符所属的作用域,用于区分同名但不同作用域的标识符。

  • 存储位置/地址(Address/Offset):在内存中的相对或绝对地址(在代码生成阶段会用到)。

  • 其他属性:例如,是否为常量、是否为函数、函数参数列表、数组大小等。

为什么符号表要用“栈式”结构?

C语言的作用域是嵌套的,而且是块作用域。为了正确处理这种嵌套关系和标识符的可见性,符号表通常采用**栈式(Stack-based)**结构。

(这里想象一个栈式符号表的图: 栈顶是当前作用域的哈希表,栈底是全局作用域的哈希表。 当进入一个新作用域(如函数体、if 语句块)时,一个新的哈希表会被压入栈顶。 当退出一个作用域时,栈顶的哈希表会被弹出。 查找符号时,从栈顶(当前作用域)开始向上查找,直到找到或到达栈底。 )

具体实现通常是:

  • 每个作用域对应一个独立的哈希表(Hash Table)

  • 这些哈希表被组织成一个

  • 进入新作用域:创建一个新的哈希表,并将其压入栈顶。

  • 退出作用域:弹出栈顶的哈希表。

  • 添加符号:将符号添加到当前作用域(栈顶哈希表)。

  • 查找符号:从栈顶开始,逐级向下(向外层作用域)查找,直到找到第一个匹配的符号,或者遍历完所有作用域。这种查找顺序自然地实现了局部变量遮蔽全局变量的规则。

抽象语法树(AST)的增强与遍历

语义分析的核心工作就是遍历AST。在遍历过程中,它会:

  1. 构建和维护符号表:在进入新的作用域节点(如函数声明、复合语句)时,压入新的符号表;在退出时,弹出符号表。在声明节点(如变量声明)时,向当前符号表添加符号。

  2. 执行语义检查:根据AST节点的类型,执行相应的类型检查、声明检查等。

  3. 向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.hsymbol_table.c

我们将单独创建 symbol_table.hsymbol_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.hsemantic_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) 中的 ab 不会被作为正式的参数声明添加到符号表中。在 sum 函数内部,ab 如果被使用,当前版本会因为找不到声明而报错。这是为了简化本阶段的实现,后续可以扩展函数参数解析和符号表管理。

  • printf 函数printf("Final value: %d\n", x); 这样的函数调用,目前只会被解析为一个 AST_EXPRESSION_STATEMENT,其内部的字符串字面量和变量 x 会被分析,但 printf 本身不会被识别为一个预定义函数,也不会检查其参数类型。这属于更高级的语义分析范畴。

  • 变量遮蔽:在 main 函数的 if 块中,int x = 5; 在标准C中是合法的,它会创建一个新的局部变量 x 遮蔽外部的 x。但为了简化符号表查找和重复定义检查,我们目前的 add_symbol 函数在当前作用域发现同名就会报错。这是手撸编译器简化处理的一个例子,实际编译器会更精细地处理作用域嵌套和变量遮蔽。

编译和运行
  1. 保存文件:将上述代码块分别保存为 ast.h, ast.c, symbol_table.h, symbol_table.c, semantic_analyzer.h, semantic_analyzer.c, lexer.h, lexer.ctest.c。确保所有文件都是最新版本。

  2. 编译:在终端中使用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标准编译。

  3. 运行

    ./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_PROGRAMfunction_declarations 列表 -> 对于每个 AST_FUNCTION_DECLARATION 节点: 将函数名添加到全局符号表 -> enter_scope() (进入函数作用域) -> 分析函数体 (AST_COMPOUND_STATEMENT) -> enter_scope() (进入块作用域) -> 遍历 AST_COMPOUND_STATEMENTstatements 列表 -> 对于每个 AST_STATEMENT 节点: 根据类型分发到 analyze_variable_declaration(), analyze_expression(), analyze_if_statement(), analyze_while_statement(), analyze_return_statement() -> 在这些分析函数中: -> 如果遇到 AST_IDENTIFIERlookup_symbol() 检查是否声明,获取类型。 -> 如果遇到 AST_VARIABLE_DECLARATIONadd_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语言底层原理的兄弟们看到!
 

手撸编译器系列(四)—— 中间代码生成

引言:从“思想”到“蓝图”——编译器的“桥梁”艺术!

兄弟们,咱们的“手撸编译器”已经走到了第四站:

  1. 词法分析:把C代码的字符流变成“词法单元”(Token)。

  2. 语法分析:把Token流组织成“抽象语法树”(AST),代码的“骨架”。

  3. 语义分析:在AST上填充“血肉”,检查代码的逻辑和类型,并进行“修饰”。

现在,我们有了一棵经过语义检查、带有丰富类型信息的AST。这棵树已经完整地表达了C代码的“思想”和“意义”。但CPU可不认识什么AST!它只认识冰冷的二进制指令。

从高层的AST到低层的机器码之间,通常会有一个或多个中间表示(Intermediate Representation, IR)。中间代码生成阶段,就是把AST翻译成这种IR。

你可能会问:

  • 为什么不直接从AST生成机器码?非要多加一个“中间”环节?

  • 中间代码长什么样?它有什么用?

  • 编译器是怎么把AST变成中间代码的?

今天,咱们就来彻底搞懂这些问题,并且,咱们还要亲手“手撸”一个简易的C语言中间代码生成器,让它把咱们的AST变成一份清晰的“执行蓝图”!

中间代码:编译器的“桥梁”——连接高层与底层!

中间代码(Intermediate Code)是源代码程序的一种独立于机器的表示形式。它介于源代码和目标代码之间,扮演着编译器内部的“通用语言”角色。

为什么需要中间代码?

中间代码的存在,给编译器带来了巨大的好处:

  1. 简化编译器结构

    • 前端(词法、语法、语义分析)负责将源代码翻译成IR。

    • 后端(代码优化、目标代码生成)负责将IR翻译成目标代码。

    • 这样,前端和后端可以相对独立地开发和维护。

    • 如果我们要支持多种源语言(如C、Java)或多种目标机器(如x86、ARM),只需要修改相应的前端或后端,而IR本身保持不变,大大提高了编译器的可重用性。

  2. 方便代码优化

    • 在IR上进行优化比在AST上优化更容易,因为IR更接近机器指令,但又不像机器指令那样受限于特定硬件。

    • IR上的优化可以独立于源语言和目标机器,实现“一次优化,到处受益”。

  3. 便于目标代码生成

    • 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.hir_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_declarationgenerate_ir_expression 中的函数调用处理非常简化。calculate(val1, val2) 这样的调用,我们只生成 IR_CALL 指令,但没有处理参数传递(IR_PARAM 指令)和返回值接收的完整逻辑。这需要更复杂的运行时栈帧管理,超出本阶段的简化目标。

  • 全局变量global_var 目前只在语义分析阶段被识别并添加到符号表,但我们没有为其生成特定的IR指令来分配全局内存,这通常在目标代码生成阶段处理。

  • 字符串字面量char *message = "Hello IR!"; 会生成 IR_DECLARE_VAR messageIR_ASSIGN message, "Hello IR!"。字符串字面量的实际存储和地址分配通常在数据段进行,并在目标代码生成阶段处理。

  • 自增/自减val1++; 会被翻译成 IR_INCREMENT temp, val1IR_ASSIGN val1, temp 这种形式,或者直接 IR_INCREMENT val1, val1 (如果指令集支持)。我们这里简化为 IR_INCREMENT result, operand

编译和运行
  1. 保存文件:将上述代码块分别保存为 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.ctest.c。确保所有文件都是最新版本。

  2. 编译:在终端中使用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
    
    
  3. 运行

    ./mycompiler test.c
    
    

你将看到程序输出词法分析的Token流、语法分析器构建的AST、语义分析后的AST(带有类型标注),以及最终由中间代码生成器产生的三地址码(IR)指令序列

原理分析与逻辑剖析:中间代码生成器是如何“绘制蓝图”的?

咱们的中间代码生成器,其核心任务就是将AST这个“高层思想”转化为三地址码这个“低层蓝图”。它通过递归遍历AST,并根据每个AST节点的类型,发出(emit)相应的IR指令。

1. 临时变量和标签的生成
  • new_temp():在表达式求值过程中,为了存储中间结果,我们需要引入临时变量(Temporary Variables)。这些临时变量在源代码中并不存在,它们是编译器为了方便计算而创建的。例如 a = b + c * 2; 中的 t1t2new_temp() 函数负责生成唯一的临时变量名,确保它们不会冲突。

  • new_label():在处理控制流语句(如 ifwhile)时,我们需要**标签(Labels)**来标记代码的跳转目标。new_label() 函数负责生成唯一的标签名。

2. emit_ir() 函数:IR指令的“打印机”

emit_ir(opcode, result, op1, op2) 函数是中间代码生成的核心。每当翻译器决定生成一条IR指令时,它就调用 emit_ir。这个函数会:

  • 创建一个新的 IRInstruction 结构体。

  • 填充其 opcoderesultoperand1operand2 字段。

  • 将这条指令添加到全局的 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_INTOPERAND_LITERAL_STR 操作数。

    • 标识符(AST_IDENTIFIER:创建 OPERAND_VAR 操作数。

    • 二元表达式(AST_BINARY_EXPRESSION

      • 递归翻译左右子表达式,得到它们的 Operand

      • 生成一个新的临时变量 t_new 作为结果。

      • 根据操作符(+, -, *, /, ==, && 等),发射一条对应的三地址码指令,例如 t_new = op1_result OP op2_result

      • 返回 t_newOperand

    • 一元表达式(AST_UNARY_EXPRESSION

      • 递归翻译操作数,得到其 Operand

      • 生成一个新的临时变量 t_new 作为结果。

      • 根据操作符(-, !, ++ 等),发射一条对应的三地址码指令,例如 t_new = OP op_result

      • 返回 t_newOperand

    • 赋值表达式(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_elseL_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_startL_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_PROGRAMfunction_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_STATEMENTstatements 列表 -> 对于每个 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







 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值