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

神图镇楼 ,2w行手写代码见证总结、归纳、开源化过程:


代码文件夹的内容:



首先,开头附上神图一张,这是为什么本人手撸完可能是全网最适合初学者学习的c语言教程之后,收录了将近两万行代码,突然心血来潮搞这么一出编译器、手撸os、手撸最小化系统、手撸100例趣味例程:

语法、100例子、牛客大厂题目单100、课程自学到现在

其实也就是我个人技术博客的一个延申和mark见证,两个月的时间写了将近两万行c,从语法到大场面试到最终的自学c语言所有常见考点,再写这么一个超级硬核超级底层的内容,也更多是为了给自己一个挑战

以上:引子
————————————————

                 
 

彻底掀开C语言的“底裤”:手撸编译器系列(一)—— 词法分析的惊天秘密!

引言:C语言为什么是YYDS?编译器的“黑箱”之谜!

兄弟们,咱们先来聊聊C语言。为啥这么多年过去了,各种新潮语言层出不穷,C语言依然稳坐“编程语言鄙视链”的顶端,被无数高手奉为“永远的神”(YYDS)?

因为C语言,它真的“够底层”!它能直接操作内存,能和操作系统“亲密接触”,能写出性能爆炸的代码,是操作系统、嵌入式、高性能计算等领域的绝对王者。但与此同时,它的“底层”也让很多人望而却步,尤其是它背后那个默默无闻、却又至关重要的“黑箱”——编译器

你是不是也曾疑惑:

  • 我写的C代码,比如一个简单的 int a = 10;,它是怎么变成CPU能执行的 010101... 这样的机器指令的?

  • 编译器到底是个什么“妖魔鬼怪”,能把人类能懂的文字,变成机器能懂的二进制?

  • 为什么那么多C语言程序员,一辈子都在用编译器,却从来没想过去了解它,甚至去“手撸”一个?

今天,我就是要打破这个“黑箱”,带你深入编译器的腹地,亲手触摸它的脉搏。咱们要做的,就是从最原始的字符开始,一步步构建一个能把C语言代码变成机器能执行的程序的神器!

这绝对不是什么“屠龙之术”,而是每一个想真正吃透C语言、想深入计算机底层原理的程序员都应该掌握的“葵花宝典”!

编译器的“庐山真面目”:它到底是个啥玩意儿?

在咱们开始“手撸”之前,先来个高屋建瓴的概览。编译器,说白了,就是一个翻译官。它把一种编程语言(比如C语言)写的程序,翻译成另一种语言(比如机器语言或者汇编语言)写的程序。

整个翻译过程,就像咱们做饭一样,它不是一蹴而就的,而是分成好几个“工序”:

(这里想象一个思维导图:中心是“编译器”,分支出去是各个阶段)

  1. 预处理(Preprocessing):这步不是编译器做的,但它是编译的第一个环节。它会处理 #include#define 等预处理指令,把宏展开,把头文件内容包含进来,生成一个“干净”的C代码文件。

  2. 编译(Compilation):这才是编译器真正开始干活的地方!它把预处理后的C代码翻译成汇编语言。这步又细分为好几个子阶段,咱们今天主要就围绕它展开:

    • 词法分析(Lexical Analysis):把源代码的字符流分解成一个个有意义的“单词”(Token)。

    • 语法分析(Syntax Analysis):根据语言的语法规则,把这些“单词”组织成有意义的“句子”(语法树)。

    • 语义分析(Semantic Analysis):检查这些“句子”的含义是否合法,比如变量有没有定义、类型是否匹配等。

    • 中间代码生成(Intermediate Code Generation):把语法树转换成一种更接近机器语言,但又独立于具体机器的“中间代码”。

    • 代码优化(Code Optimization):对中间代码进行各种优化,让生成的程序跑得更快、占用内存更少。

    • 目标代码生成(Target Code Generation):把优化后的中间代码翻译成特定机器的汇编语言。

  3. 汇编(Assembly):汇编器(Assembler)登场!它把汇编语言翻译成机器码(目标文件,.o.obj)。

  4. 链接(Linking):链接器(Linker)出马!它把多个目标文件以及程序用到的库文件(比如 printf 函数所在的库)组合起来,生成一个最终的可执行文件。

(这里想象一个流程图:C Source -> Preprocessor -> Clean C Code -> Compiler (Lexical Analysis -> Syntax Analysis -> Semantic Analysis -> Intermediate Code Gen -> Code Optimization -> Target Code Gen) -> Assembly Code -> Assembler -> Object File -> Linker -> Executable File)

是不是有点懵?没关系!咱们今天就聚焦在编译器的第一步,也是最基础、最直观的一步——词法分析

词法分析:编译器的“眼睛”——把代码“大卸八块”!

想象一下,你拿到一本英文书,要把它翻译成中文。你第一步会做什么?是不是先把书上的文字一个一个地“读”出来,识别出每个单词,比如 HelloWorldisabook?你不会一开始就去分析句子的主谓宾定状补,对吧?

词法分析器(Lexical Analyzer),也叫扫描器(Scanner)或者分词器(Tokenizer),就是编译器的“眼睛”,它干的就是这个活儿!

它的任务非常简单粗暴,但又极其重要:

把你的C语言源代码(一串长长的字符流),分解成一个个有独立意义的最小单位——“词法单元”(Token)。

这些“词法单元”就像咱们语言里的“单词”一样,它们是语法分析的基础。

什么是“词法单元”(Token)?

一个“词法单元”(Token)通常包含两个部分:

  1. 类型(Type):这个“单词”属于什么种类?是关键字?标识符?运算符?还是常量?

  2. 值(Value/Lexeme):这个“单词”具体是什么?比如,如果类型是“标识符”,值可能是“main”;如果类型是“整数常量”,值可能是“100”。

咱们举个栗子:

C语言代码:int main() { return 0; }

经过词法分析后,它可能会被分解成这样一串Token流:

Token 类型

Token 值(词素)

描述

TOKEN_KEYWORD_INT

int

关键字 int

TOKEN_IDENTIFIER

main

标识符 main

TOKEN_LPAREN

(

左括号

TOKEN_RPAREN

)

右括号

TOKEN_LBRACE

{

左大括号

TOKEN_KEYWORD_RETURN

return

关键字 return

TOKEN_INT_LITERAL

0

整数常量 0

TOKEN_SEMICOLON

;

分号

TOKEN_RBRACE

}

右大括号

TOKEN_EOF

EOF

文件结束符(表示所有代码都已处理完毕)

看到了吗?源代码中的空格、换行、注释等“不重要”的字符,在词法分析阶段就会被无情地抛弃!因为它们对程序的语义没有直接影响。

词法分析器的工作原理:有限状态机(FSM)

词法分析器是如何识别出这些Token的呢?它通常是基于**有限状态机(Finite State Machine, FSM)**来实现的。

想象一下,词法分析器就像一个“读心术”大师,它一个字符一个字符地读取源代码,根据当前的字符和它之前“读到”的字符,来判断自己处于什么“状态”,并决定接下来要识别什么类型的Token。

(这里想象一个简化的状态机图:

  • 初始状态 -> 读到字母 -> 进入“标识符/关键字”状态

  • “标识符/关键字”状态 -> 读到字母/数字 -> 保持当前状态

  • “标识符/关键字”状态 -> 读到非字母数字 -> 识别出标识符/关键字,回到初始状态

  • 初始状态 -> 读到数字 -> 进入“数字”状态

  • “数字”状态 -> 读到数字 -> 保持当前状态

  • “数字”状态 -> 读到非数字 -> 识别出数字,回到初始状态

  • 初始状态 -> 读到 + -> 识别出 +,回到初始状态

  • 初始状态 -> 读到 = -> 进入“等号”状态

  • “等号”状态 -> 读到 = -> 识别出 ==,回到初始状态

  • “等号”状态 -> 读到非 = -> 识别出 =,回到初始状态

  • ...以此类推,处理各种Token)

这个过程,就是不断地匹配模式。比如:

  • 标识符/关键字:由字母或下划线开头,后面跟着字母、数字或下划线。

  • 数字常量:由数字组成。

  • 字符串常量:由双引号 " 包裹起来的字符序列。

  • 运算符+, -, *, /, ==, !=, &&, || 等。

  • 分隔符(, ), {, }, ;, , 等。

手撸一个简易C语言词法分析器:让理论照进现实!

光说不练假把式!现在,咱们就来亲手写一个简易的C语言词法分析器。这个词法分析器能够识别C语言中的:

  • 关键字int, return, if, else, while, for 等(咱们先实现几个常用的)。

  • 标识符:变量名、函数名等。

  • 整数常量:比如 123, 0

  • 字符串常量:比如 "hello", "world"

  • 运算符+, -, *, /, =, ==, !=, >, <, >=, <=, &&, ||, !, &, |, ^, ~, <<, >> 等。

  • 分隔符(, ), {, }, [, ], ;, ,

  • 注释:单行注释 // 和多行注释 /* ... */

  • 空白字符:空格、制表符、换行符。

咱们的目标是,给它一段C代码,它能吐出一串Token流。

核心思路与数据结构

  1. 源代码管理:我们需要一个机制来读取源代码,一个字符一个字符地读,并且能“偷看”下一个字符。

  2. Token结构:定义一个结构体来表示一个Token,包含类型和值。

  3. Token类型枚举:用一个枚举类型来定义所有可能的Token类型。

  4. 关键字表:用一个数据结构(比如数组或哈希表)来存储C语言的关键字,方便快速查找。

代码实现:lexer.hlexer.c

为了代码的清晰和模块化,我们将词法分析器的代码分成两个文件:lexer.h (头文件,定义接口和数据结构) 和 lexer.c (源文件,实现具体逻辑)。

lexer.h:定义Token类型和结构
#ifndef LEXER_H
#define LEXER_H

#include <stdio.h>   // For FILE, EOF
#include <stdlib.h>  // For malloc, free
#include <string.h>  // For strdup, strcmp
#include <stdbool.h> // For bool type

// 定义所有可能的词法单元类型(Token Type)
// 这是一个枚举,每个成员代表一种Token类型
typedef enum {
    // 特殊类型
    TOKEN_EOF = 0,             // 文件结束符 (End Of File)
    TOKEN_UNKNOWN,             // 未知或非法字符

    // 关键字 (Keywords)
    TOKEN_KEYWORD_INT,         // int
    TOKEN_KEYWORD_RETURN,      // return
    TOKEN_KEYWORD_IF,          // if
    TOKEN_KEYWORD_ELSE,        // else
    TOKEN_KEYWORD_WHILE,       // while
    TOKEN_KEYWORD_FOR,         // for
    TOKEN_KEYWORD_VOID,        // void
    TOKEN_KEYWORD_CHAR,        // char
    TOKEN_KEYWORD_SHORT,       // short
    TOKEN_KEYWORD_LONG,        // long
    TOKEN_KEYWORD_FLOAT,       // float
    TOKEN_KEYWORD_DOUBLE,      // double
    TOKEN_KEYWORD_STRUCT,      // struct
    TOKEN_KEYWORD_UNION,       // union
    TOKEN_KEYWORD_ENUM,        // enum
    TOKEN_KEYWORD_TYPEDEF,     // typedef
    TOKEN_KEYWORD_SIZEOF,      // sizeof
    TOKEN_KEYWORD_BREAK,       // break
    TOKEN_KEYWORD_CONTINUE,    // continue
    TOKEN_KEYWORD_SWITCH,      // switch
    TOKEN_KEYWORD_CASE,        // case
    TOKEN_KEYWORD_DEFAULT,     // default
    TOKEN_KEYWORD_DO,          // do
    TOKEN_KEYWORD_GOTO,        // goto
    TOKEN_KEYWORD_CONST,       // const
    TOKEN_KEYWORD_VOLATILE,    // volatile
    TOKEN_KEYWORD_EXTERN,      // extern
    TOKEN_KEYWORD_STATIC,      // static
    TOKEN_KEYWORD_AUTO,        // auto
    TOKEN_KEYWORD_REGISTER,    // register
    TOKEN_KEYWORD_SIGNED,      // signed
    TOKEN_KEYWORD_UNSIGNED,    // unsigned

    // 标识符 (Identifiers)
    TOKEN_IDENTIFIER,          // 变量名、函数名等

    // 常量 (Literals)
    TOKEN_INT_LITERAL,         // 整数常量 (e.g., 123, 0)
    TOKEN_STRING_LITERAL,      // 字符串常量 (e.g., "hello world")
    // TOKEN_FLOAT_LITERAL,    // 浮点数常量 (暂时不实现,后续可扩展)
    // TOKEN_CHAR_LITERAL,     // 字符常量 (暂时不实现,后续可扩展)

    // 运算符 (Operators) - 单字符
    TOKEN_PLUS,                // +
    TOKEN_MINUS,               // -
    TOKEN_STAR,                // *
    TOKEN_SLASH,               // /
    TOKEN_MODULO,              // %
    TOKEN_ASSIGN,              // =
    TOKEN_LT,                  // <
    TOKEN_GT,                  // >
    TOKEN_AMPERSAND,           // & (位与 或 地址运算符)
    TOKEN_PIPE,                // | (位或)
    TOKEN_CARET,               // ^ (位异或)
    TOKEN_TILDE,               // ~ (位非)
    TOKEN_EXCLAMATION,         // ! (逻辑非)
    TOKEN_DOT,                 // . (成员访问)
    TOKEN_COMMA,               // , (逗号)
    TOKEN_COLON,               // : (冒号)
    TOKEN_QUESTION,            // ? (三目运算符)

    // 运算符 (Operators) - 双字符
    TOKEN_EQ,                  // == (等于)
    TOKEN_NE,                  // != (不等于)
    TOKEN_LE,                  // <= (小于等于)
    TOKEN_GE,                  // >= (大于等于)
    TOKEN_AND,                 // && (逻辑与)
    TOKEN_OR,                  // || (逻辑或)
    TOKEN_INC,                 // ++ (自增)
    TOKEN_DEC,                 // -- (自减)
    TOKEN_PLUS_ASSIGN,         // +=
    TOKEN_MINUS_ASSIGN,        // -=
    TOKEN_STAR_ASSIGN,         // *=
    TOKEN_SLASH_ASSIGN,        // /=
    TOKEN_MODULO_ASSIGN,       // %=
    TOKEN_LSHIFT,              // << (左移)
    TOKEN_RSHIFT,              // >> (右移)
    TOKEN_LSHIFT_ASSIGN,       // <<=
    TOKEN_RSHIFT_ASSIGN,       // >>=
    TOKEN_AMPERSAND_ASSIGN,    // &=
    TOKEN_PIPE_ASSIGN,         // |=
    TOKEN_CARET_ASSIGN,        // ^=
    TOKEN_ARROW,               // -> (成员访问)

    // 分隔符 (Delimiters)
    TOKEN_LPAREN,              // (
    TOKEN_RPAREN,              // )
    TOKEN_LBRACE,              // {
    TOKEN_RBRACE,              // }
    TOKEN_LBRACKET,            // [
    TOKEN_RBRACKET,            // ]
    TOKEN_SEMICOLON,           // ;
} TokenType;

// Token结构体定义
// 每个Token都包含其类型和对应的字符串值(词素)
typedef struct {
    TokenType type;    // Token的类型
    char *value;       // Token的字符串值(词素),如 "main", "123", "+"
    int line;          // Token所在的行号
    int column;        // Token所在的列号
} Token;

// 全局的源代码指针和当前字符
// current_char_g: 当前正在处理的字符
// source_code_g: 存储整个源代码的字符串
// current_pos_g: 当前字符在source_code_g中的位置
// current_line_g: 当前字符所在的行号
// current_column_g: 当前字符所在的列号
extern char current_char_g;
extern const char *source_code_g;
extern int current_pos_g;
extern int current_line_g;
extern int current_column_g;

// 词法分析器初始化函数
// 参数:filePath - C语言源代码文件的路径
// 返回值:成功返回true,失败返回false
bool lexer_init(const char *filePath);

// 获取下一个词法单元(Token)
// 这是词法分析器的核心函数,每次调用都会返回一个Token
// 返回值:指向新分配的Token的指针,需要调用者负责释放
Token *lexer_getToken();

// 释放Token内存的辅助函数
void free_token(Token *token);

// 词法分析器清理函数
void lexer_cleanup();

#endif // LEXER_H

lexer.c:词法分析器的核心实现
#include "lexer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // For isalnum, isalpha, isdigit, isspace

// 全局变量的定义
char current_char_g;
const char *source_code_g = NULL;
int current_pos_g = 0;
int current_line_g = 1;
int current_column_g = 1;

// -----------------------------------------------------------------------------
// 辅助函数:字符处理
// -----------------------------------------------------------------------------

// 读取下一个字符并更新位置信息
static void advance() {
    // 如果当前位置已经到达源代码末尾,则将当前字符设为EOF
    if (source_code_g[current_pos_g] == '\0') {
        current_char_g = EOF;
    } else {
        // 读取当前位置的字符
        current_char_g = source_code_g[current_pos_g];
        // 更新列号
        current_column_g++;
        // 如果是换行符,则更新行号和列号
        if (current_char_g == '\n') {
            current_line_g++;
            current_column_g = 1; // 列号重置为1
        }
        // 移动到下一个字符
        current_pos_g++;
    }
}

// 偷看下一个字符,但不移动当前位置
static char peek() {
    // 如果下一个位置已经到达源代码末尾,则返回EOF
    if (source_code_g[current_pos_g] == '\0') {
        return EOF;
    }
    // 返回下一个字符
    return source_code_g[current_pos_g];
}

// 检查字符是否是空白字符(空格、制表符、换行符、回车符等)
static bool is_whitespace(char c) {
    return isspace(c);
}

// 检查字符是否是字母(包括下划线)
static bool is_alpha(char c) {
    return isalpha(c) || c == '_';
}

// 检查字符是否是数字
static bool is_digit(char c) {
    return isdigit(c);
}

// 检查字符是否是字母或数字(包括下划线)
static bool is_alphanumeric(char c) {
    return isalnum(c) || c == '_';
}

// -----------------------------------------------------------------------------
// Token创建函数
// -----------------------------------------------------------------------------

// 创建一个新的Token并分配内存
// 参数:type - Token的类型
//       value - Token的字符串值(词素)
//       line - Token所在的行号
//       column - Token所在的列号
// 返回值:指向新分配的Token的指针
static Token *create_token(TokenType type, const char *value, int line, int column) {
    Token *token = (Token *)malloc(sizeof(Token));
    if (!token) {
        fprintf(stderr, "Error: Failed to allocate memory for token.\n");
        exit(EXIT_FAILURE); // 内存分配失败,直接退出
    }
    token->type = type;
    token->value = value ? strdup(value) : NULL; // 复制字符串值,避免悬空指针
    token->line = line;
    token->column = column;
    return token;
}

// 释放Token内存
void free_token(Token *token) {
    if (token) {
        free(token->value); // 释放Token值字符串的内存
        free(token);        // 释放Token结构体本身的内存
    }
}

// -----------------------------------------------------------------------------
// 关键字查找表
// -----------------------------------------------------------------------------

// 结构体用于存储关键字及其对应的TokenType
typedef struct {
    const char *keyword;
    TokenType type;
} KeywordEntry;

// C语言关键字表
// 这是一个静态常量数组,存储了所有我们支持的C语言关键字
static const KeywordEntry keywords[] = {
    {"int", TOKEN_KEYWORD_INT},
    {"return", TOKEN_KEYWORD_RETURN},
    {"if", TOKEN_KEYWORD_IF},
    {"else", TOKEN_KEYWORD_ELSE},
    {"while", TOKEN_KEYWORD_WHILE},
    {"for", TOKEN_KEYWORD_FOR},
    {"void", TOKEN_KEYWORD_VOID},
    {"char", TOKEN_KEYWORD_CHAR},
    {"short", TOKEN_KEYWORD_SHORT},
    {"long", TOKEN_KEYWORD_LONG},
    {"float", TOKEN_KEYWORD_FLOAT},
    {"double", TOKEN_KEYWORD_DOUBLE},
    {"struct", TOKEN_KEYWORD_STRUCT},
    {"union", TOKEN_KEYWORD_UNION},
    {"enum", TOKEN_KEYWORD_ENUM},
    {"typedef", TOKEN_KEYWORD_TYPEDEF},
    {"sizeof", TOKEN_KEYWORD_SIZEOF},
    {"break", TOKEN_KEYWORD_BREAK},
    {"continue", TOKEN_KEYWORD_CONTINUE},
    {"switch", TOKEN_KEYWORD_SWITCH},
    {"case", TOKEN_KEYWORD_CASE},
    {"default", TOKEN_KEYWORD_DEFAULT},
    {"do", TOKEN_KEYWORD_DO},
    {"goto", TOKEN_KEYWORD_GOTO},
    {"const", TOKEN_KEYWORD_CONST},
    {"volatile", TOKEN_KEYWORD_VOLATILE},
    {"extern", TOKEN_KEYWORD_EXTERN},
    {"static", TOKEN_KEYWORD_STATIC},
    {"auto", TOKEN_KEYWORD_AUTO},
    {"register", TOKEN_KEYWORD_REGISTER},
    {"signed", TOKEN_KEYWORD_SIGNED},
    {"unsigned", TOKEN_KEYWORD_UNSIGNED},
    {NULL, TOKEN_UNKNOWN} // 哨兵值,表示列表结束
};

// 查找给定的字符串是否是关键字
// 参数:identifier - 待查找的字符串
// 返回值:如果是关键字,返回对应的TokenType;否则返回TOKEN_IDENTIFIER
static TokenType lookup_keyword(const char *identifier) {
    for (int i = 0; keywords[i].keyword != NULL; ++i) {
        if (strcmp(identifier, keywords[i].keyword) == 0) {
            return keywords[i].type; // 找到匹配的关键字
        }
    }
    return TOKEN_IDENTIFIER; // 不是关键字,则是一个普通的标识符
}

// -----------------------------------------------------------------------------
// 词法分析核心逻辑
// -----------------------------------------------------------------------------

// 跳过空白字符(空格、制表符、换行符等)和注释
static void skip_whitespace_and_comments() {
    while (current_char_g != EOF) {
        if (is_whitespace(current_char_g)) {
            // 如果是空白字符,直接跳过
            advance();
        } else if (current_char_g == '/') {
            // 可能是注释或除法运算符
            if (peek() == '/') {
                // 单行注释 //
                // 跳过当前字符 '/'
                advance();
                // 跳过下一个字符 '/'
                advance();
                // 一直跳过直到行尾或文件结束
                while (current_char_g != '\n' && current_char_g != EOF) {
                    advance();
                }
                // 跳过换行符(如果存在)
                if (current_char_g == '\n') {
                    advance();
                }
            } else if (peek() == '*') {
                // 多行注释 /* ... */
                // 跳过当前字符 '/'
                advance();
                // 跳过下一个字符 '*'
                advance();
                // 循环直到找到 "*/" 或文件结束
                while (!(current_char_g == '*' && peek() == '/') && current_char_g != EOF) {
                    advance();
                }
                // 如果找到了 "*/",跳过 '*' 和 '/'
                if (current_char_g == '*' && peek() == '/') {
                    advance(); // 跳过 '*'
                    advance(); // 跳过 '/'
                } else {
                    // 如果文件结束了还没找到 "*/",说明注释未闭合,这是个错误
                    fprintf(stderr, "Error: Unclosed multi-line comment starting at line %d, column %d.\n", current_line_g, current_column_g - 2);
                    // 尽管是错误,我们仍然尝试继续解析,但实际编译器可能会选择终止
                    // 这里为了演示,我们继续,但在实际生产中应该更严格
                    break; // 退出循环,让后续的getToken处理EOF或下一个字符
                }
            } else {
                // 只是一个普通的除号 '/',不跳过,让主逻辑处理
                break;
            }
        } else {
            // 既不是空白字符也不是注释,退出循环
            break;
        }
    }
}

// 扫描标识符或关键字
// 从当前字符开始,读取所有符合标识符命名规则的字符,直到遇到不符合规则的字符
// 然后判断这个字符串是关键字还是普通标识符
static Token *scan_identifier_or_keyword(int start_line, int start_column) {
    char buffer[256]; // 临时缓冲区,用于存储标识符或关键字的字符串
    int i = 0;

    // 读取所有字母、数字或下划线
    while (is_alphanumeric(current_char_g) && i < sizeof(buffer) - 1) {
        buffer[i++] = current_char_g;
        advance();
    }
    buffer[i] = '\0'; // 字符串结束符

    // 查找是否是关键字
    TokenType type = lookup_keyword(buffer);
    return create_token(type, buffer, start_line, start_column);
}

// 扫描整数常量
// 从当前字符开始,读取所有数字字符,直到遇到非数字字符
static Token *scan_number(int start_line, int start_column) {
    char buffer[256]; // 临时缓冲区,用于存储数字字符串
    int i = 0;

    // 读取所有数字
    while (is_digit(current_char_g) && i < sizeof(buffer) - 1) {
        buffer[i++] = current_char_g;
        advance();
    }
    buffer[i] = '\0'; // 字符串结束符

    return create_token(TOKEN_INT_LITERAL, buffer, start_line, start_column);
}

// 扫描字符串常量
// 从当前字符开始,读取双引号内的所有字符,直到遇到另一个双引号
static Token *scan_string(int start_line, int start_column) {
    // 跳过开头的双引号
    advance();

    char buffer[1024]; // 临时缓冲区,用于存储字符串内容
    int i = 0;

    // 循环直到遇到闭合的双引号或文件结束
    while (current_char_g != '"' && current_char_g != EOF && i < sizeof(buffer) - 1) {
        if (current_char_g == '\\') { // 处理转义字符
            buffer[i++] = current_char_g; // 存储反斜杠
            advance(); // 跳过反斜杠
            if (current_char_g == EOF) {
                fprintf(stderr, "Error: Unexpected EOF in string literal after backslash at line %d, column %d.\n", start_line, start_column);
                return create_token(TOKEN_UNKNOWN, NULL, start_line, start_column);
            }
        }
        buffer[i++] = current_char_g;
        advance();
    }
    buffer[i] = '\0'; // 字符串结束符

    if (current_char_g != '"') {
        // 字符串未闭合,这是个错误
        fprintf(stderr, "Error: Unclosed string literal starting at line %d, column %d.\n", start_line, start_column);
        return create_token(TOKEN_UNKNOWN, NULL, start_line, start_column);
    }

    // 跳过闭合的双引号
    advance();

    return create_token(TOKEN_STRING_LITERAL, buffer, start_line, start_column);
}

// -----------------------------------------------------------------------------
// 词法分析器接口函数
// -----------------------------------------------------------------------------

// 词法分析器初始化
// 从文件中读取源代码到内存中
bool lexer_init(const char *filePath) {
    FILE *file = fopen(filePath, "rb"); // 使用二进制模式读取,避免文本模式的换行符转换问题
    if (!file) {
        fprintf(stderr, "Error: Could not open source file '%s'\n", filePath);
        return false;
    }

    // 获取文件大小
    fseek(file, 0, SEEK_END);
    long file_size = ftell(file);
    fseek(file, 0, SEEK_SET);

    // 分配内存来存储整个文件内容,额外加1字节用于存储字符串结束符'\0'
    char *buffer = (char *)malloc(file_size + 1);
    if (!buffer) {
        fprintf(stderr, "Error: Failed to allocate memory for source code.\n");
        fclose(file);
        return false;
    }

    // 读取文件内容到缓冲区
    size_t bytes_read = fread(buffer, 1, file_size, file);
    buffer[bytes_read] = '\0'; // 添加字符串结束符

    fclose(file);

    source_code_g = buffer; // 将全局源代码指针指向缓冲区
    current_pos_g = 0;
    current_line_g = 1;
    current_column_g = 1;

    // 预读第一个字符
    advance();

    fprintf(stdout, "Lexer initialized successfully. Source file: '%s', size: %ld bytes.\n", filePath, file_size);
    return true;
}

// 获取下一个Token的核心函数
Token *lexer_getToken() {
    // 循环直到找到一个有效的Token或者到达文件末尾
    while (current_char_g != EOF) {
        // 1. 跳过空白字符和注释
        skip_whitespace_and_comments();

        // 记录当前Token的起始位置,用于错误报告和Token创建
        int start_line = current_line_g;
        int start_column = current_column_g;

        // 如果跳过空白和注释后到达文件末尾,则返回EOF Token
        if (current_char_g == EOF) {
            return create_token(TOKEN_EOF, "EOF", start_line, start_column);
        }

        // 2. 识别标识符或关键字
        if (is_alpha(current_char_g)) {
            return scan_identifier_or_keyword(start_line, start_column);
        }

        // 3. 识别数字常量
        if (is_digit(current_char_g)) {
            return scan_number(start_line, start_column);
        }

        // 4. 识别字符串常量
        if (current_char_g == '"') {
            return scan_string(start_line, start_column);
        }

        // 5. 识别运算符和分隔符
        switch (current_char_g) {
            case '+':
                advance();
                if (current_char_g == '+') { advance(); return create_token(TOKEN_INC, "++", start_line, start_column); }
                if (current_char_g == '=') { advance(); return create_token(TOKEN_PLUS_ASSIGN, "+=", start_line, start_column); }
                return create_token(TOKEN_PLUS, "+", start_line, start_column);
            case '-':
                advance();
                if (current_char_g == '-') { advance(); return create_token(TOKEN_DEC, "--", start_line, start_column); }
                if (current_char_g == '=') { advance(); return create_token(TOKEN_MINUS_ASSIGN, "-=", start_line, start_column); }
                if (current_char_g == '>') { advance(); return create_token(TOKEN_ARROW, "->", start_line, start_column); }
                return create_token(TOKEN_MINUS, "-", start_line, start_column);
            case '*':
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_STAR_ASSIGN, "*=", start_line, start_column); }
                return create_token(TOKEN_STAR, "*", start_line, start_column);
            case '/': // 除号或注释已在 skip_whitespace_and_comments 中处理
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_SLASH_ASSIGN, "/=", start_line, start_column); }
                return create_token(TOKEN_SLASH, "/", start_line, start_column);
            case '%':
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_MODULO_ASSIGN, "%=", start_line, start_column); }
                return create_token(TOKEN_MODULO, "%", start_line, start_column);
            case '=':
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_EQ, "==", start_line, start_column); }
                return create_token(TOKEN_ASSIGN, "=", start_line, start_column);
            case '!':
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_NE, "!=", start_line, start_column); }
                return create_token(TOKEN_EXCLAMATION, "!", start_line, start_column);
            case '<':
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_LE, "<=", start_line, start_column); }
                if (current_char_g == '<') { 
                    advance(); 
                    if (current_char_g == '=') { advance(); return create_token(TOKEN_LSHIFT_ASSIGN, "<<=", start_line, start_column); }
                    return create_token(TOKEN_LSHIFT, "<<", start_line, start_column); 
                }
                return create_token(TOKEN_LT, "<", start_line, start_column);
            case '>':
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_GE, ">=", start_line, start_column); }
                if (current_char_g == '>') { 
                    advance(); 
                    if (current_char_g == '=') { advance(); return create_token(TOKEN_RSHIFT_ASSIGN, ">>=", start_line, start_column); }
                    return create_token(TOKEN_RSHIFT, ">>", start_line, start_column); 
                }
                return create_token(TOKEN_GT, ">", start_line, start_column);
            case '&':
                advance();
                if (current_char_g == '&') { advance(); return create_token(TOKEN_AND, "&&", start_line, start_column); }
                if (current_char_g == '=') { advance(); return create_token(TOKEN_AMPERSAND_ASSIGN, "&=", start_line, start_column); }
                return create_token(TOKEN_AMPERSAND, "&", start_line, start_column);
            case '|':
                advance();
                if (current_char_g == '|') { advance(); return create_token(TOKEN_OR, "||", start_line, start_column); }
                if (current_char_g == '=') { advance(); return create_token(TOKEN_PIPE_ASSIGN, "|=", start_line, start_column); }
                return create_token(TOKEN_PIPE, "|", start_line, start_column);
            case '^':
                advance();
                if (current_char_g == '=') { advance(); return create_token(TOKEN_CARET_ASSIGN, "^=", start_line, start_column); }
                return create_token(TOKEN_CARET, "^", start_line, start_column);
            case '~':
                advance(); return create_token(TOKEN_TILDE, "~", start_line, start_column);
            case '(':
                advance(); return create_token(TOKEN_LPAREN, "(", start_line, start_column);
            case ')':
                advance(); return create_token(TOKEN_RPAREN, ")", start_line, start_column);
            case '{':
                advance(); return create_token(TOKEN_LBRACE, "{", start_line, start_column);
            case '}':
                advance(); return create_token(TOKEN_RBRACE, "}", start_line, start_column);
            case '[':
                advance(); return create_token(TOKEN_LBRACKET, "[", start_line, start_column);
            case ']':
                advance(); return create_token(TOKEN_RBRACKET, "]", start_line, start_column);
            case ';':
                advance(); return create_token(TOKEN_SEMICOLON, ";", start_line, start_column);
            case ',':
                advance(); return create_token(TOKEN_COMMA, ",", start_line, start_column);
            case '.':
                // 简单的点运算符,不处理浮点数或变长参数列表的省略号
                advance(); return create_token(TOKEN_DOT, ".", start_line, start_column);
            case ':':
                advance(); return create_token(TOKEN_COLON, ":", start_line, start_column);
            case '?':
                advance(); return create_token(TOKEN_QUESTION, "?", start_line, start_column);
            default:
                // 遇到无法识别的字符,报告错误并返回UNKNOWN Token
                fprintf(stderr, "Error: Unexpected character '%c' at line %d, column %d.\n", current_char_g, start_line, start_column);
                char unknown_char_str[2] = {current_char_g, '\0'}; // 将未知字符转为字符串
                advance(); // 跳过未知字符,继续扫描
                return create_token(TOKEN_UNKNOWN, unknown_char_str, start_line, start_column);
        }
    }
    // 如果循环结束,说明已经到达文件末尾
    return create_token(TOKEN_EOF, "EOF", current_line_g, current_column_g);
}

// 词法分析器清理函数
void lexer_cleanup() {
    if (source_code_g) {
        free((void *)source_code_g); // 释放源代码缓冲区内存
        source_code_g = NULL;
    }
    fprintf(stdout, "Lexer cleaned up.\n");
}


main.c:测试我们的词法分析器

为了验证我们的词法分析器是否工作正常,我们需要一个 main 函数来调用它,并打印出识别到的Token。

#include "lexer.h"
#include <stdio.h>
#include <stdlib.h>

// 辅助函数:将TokenType转换为可读的字符串
// 用于打印Token信息时,更直观地显示Token类型
const char *token_type_to_string(TokenType type) {
    switch (type) {
        case TOKEN_EOF: return "EOF";
        case TOKEN_UNKNOWN: return "UNKNOWN";
        case TOKEN_KEYWORD_INT: return "KEYWORD_INT";
        case TOKEN_KEYWORD_RETURN: return "KEYWORD_RETURN";
        case TOKEN_KEYWORD_IF: return "KEYWORD_IF";
        case TOKEN_KEYWORD_ELSE: return "KEYWORD_ELSE";
        case TOKEN_KEYWORD_WHILE: return "KEYWORD_WHILE";
        case TOKEN_KEYWORD_FOR: return "KEYWORD_FOR";
        case TOKEN_KEYWORD_VOID: return "KEYWORD_VOID";
        case TOKEN_KEYWORD_CHAR: return "KEYWORD_CHAR";
        case TOKEN_KEYWORD_SHORT: return "KEYWORD_SHORT";
        case TOKEN_KEYWORD_LONG: return "KEYWORD_LONG";
        case TOKEN_KEYWORD_FLOAT: return "KEYWORD_FLOAT";
        case TOKEN_KEYWORD_DOUBLE: return "KEYWORD_DOUBLE";
        case TOKEN_KEYWORD_STRUCT: return "KEYWORD_STRUCT";
        case TOKEN_KEYWORD_UNION: return "KEYWORD_UNION";
        case TOKEN_KEYWORD_ENUM: return "KEYWORD_ENUM";
        case TOKEN_KEYWORD_TYPEDEF: return "KEYWORD_TYPEDEF";
        case TOKEN_KEYWORD_SIZEOF: return "KEYWORD_SIZEOF";
        case TOKEN_KEYWORD_BREAK: return "KEYWORD_BREAK";
        case TOKEN_KEYWORD_CONTINUE: return "KEYWORD_CONTINUE";
        case TOKEN_KEYWORD_SWITCH: return "KEYWORD_SWITCH";
        case TOKEN_KEYWORD_CASE: return "KEYWORD_CASE";
        case TOKEN_KEYWORD_DEFAULT: return "KEYWORD_DEFAULT";
        case TOKEN_KEYWORD_DO: return "KEYWORD_DO";
        case TOKEN_KEYWORD_GOTO: return "KEYWORD_GOTO";
        case TOKEN_KEYWORD_CONST: return "KEYWORD_CONST";
        case TOKEN_KEYWORD_VOLATILE: return "KEYWORD_VOLATILE";
        case TOKEN_KEYWORD_EXTERN: return "KEYWORD_EXTERN";
        case TOKEN_KEYWORD_STATIC: return "KEYWORD_STATIC";
        case TOKEN_KEYWORD_AUTO: return "KEYWORD_AUTO";
        case TOKEN_KEYWORD_REGISTER: return "KEYWORD_REGISTER";
        case TOKEN_KEYWORD_SIGNED: return "KEYWORD_SIGNED";
        case TOKEN_KEYWORD_UNSIGNED: return "KEYWORD_UNSIGNED";
        case TOKEN_IDENTIFIER: return "IDENTIFIER";
        case TOKEN_INT_LITERAL: return "INT_LITERAL";
        case TOKEN_STRING_LITERAL: return "STRING_LITERAL";
        case TOKEN_PLUS: return "PLUS";
        case TOKEN_MINUS: return "MINUS";
        case TOKEN_STAR: return "STAR";
        case TOKEN_SLASH: return "SLASH";
        case TOKEN_MODULO: return "MODULO";
        case TOKEN_ASSIGN: return "ASSIGN";
        case TOKEN_LT: return "LT";
        case TOKEN_GT: return "GT";
        case TOKEN_AMPERSAND: return "AMPERSAND";
        case TOKEN_PIPE: return "PIPE";
        case TOKEN_CARET: return "CARET";
        case TOKEN_TILDE: return "TILDE";
        case TOKEN_EXCLAMATION: return "EXCLAMATION";
        case TOKEN_DOT: return "DOT";
        case TOKEN_COMMA: return "COMMA";
        case TOKEN_COLON: return "COLON";
        case TOKEN_QUESTION: return "QUESTION";
        case TOKEN_EQ: return "EQ";
        case TOKEN_NE: return "NE";
        case TOKEN_LE: return "LE";
        case TOKEN_GE: return "GE";
        case TOKEN_AND: return "AND";
        case TOKEN_OR: return "OR";
        case TOKEN_INC: return "INC";
        case TOKEN_DEC: return "DEC";
        case TOKEN_PLUS_ASSIGN: return "PLUS_ASSIGN";
        case TOKEN_MINUS_ASSIGN: return "MINUS_ASSIGN";
        case TOKEN_STAR_ASSIGN: return "STAR_ASSIGN";
        case TOKEN_SLASH_ASSIGN: return "SLASH_ASSIGN";
        case TOKEN_MODULO_ASSIGN: return "MODULO_ASSIGN";
        case TOKEN_LSHIFT: return "LSHIFT";
        case TOKEN_RSHIFT: return "RSHIFT";
        case TOKEN_LSHIFT_ASSIGN: return "LSHIFT_ASSIGN";
        case TOKEN_RSHIFT_ASSIGN: return "RSHIFT_ASSIGN";
        case TOKEN_AMPERSAND_ASSIGN: return "AMPERSAND_ASSIGN";
        case TOKEN_PIPE_ASSIGN: return "PIPE_ASSIGN";
        case TOKEN_CARET_ASSIGN: return "CARET_ASSIGN";
        case TOKEN_ARROW: return "ARROW";
        case TOKEN_LPAREN: return "LPAREN";
        case TOKEN_RPAREN: return "RPAREN";
        case TOKEN_LBRACE: return "LBRACE";
        case TOKEN_RBRACE: return "RBRACE";
        case TOKEN_LBRACKET: return "LBRACKET";
        case TOKEN_RBRACKET: return "RBRACKET";
        case TOKEN_SEMICOLON: return "SEMICOLON";
        default: return "UNKNOWN_TYPE";
    }
}

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];

    // 初始化词法分析器
    if (!lexer_init(source_file_path)) {
        fprintf(stderr, "Failed to initialize lexer for file: %s\n", source_file_path);
        return EXIT_FAILURE;
    }

    Token *token;
    // 循环获取并打印所有Token,直到遇到文件结束符EOF
    do {
        token = lexer_getToken(); // 获取下一个Token
        // 打印Token信息:类型、值、所在行号、列号
        printf("Token: Type = %-20s, Value = \"%s\", Line = %d, Column = %d\n",
               token_type_to_string(token->type),
               token->value ? token->value : "(null)", // 如果value是NULL,打印"(null)"
               token->line,
               token->column);

        // 如果Token类型不是EOF,则释放当前Token的内存,准备获取下一个
        if (token->type != TOKEN_EOF) {
            free_token(token);
        }
    } while (token->type != TOKEN_EOF); // 当遇到EOF Token时,循环结束

    // 释放最后一个EOF Token的内存
    free_token(token);

    // 清理词法分析器(释放源代码缓冲区内存)
    lexer_cleanup();

    printf("\nLexical analysis completed.\n");
    return EXIT_SUCCESS;
}


示例C语言源代码文件:test.c

为了测试,咱们创建一个 test.c 文件:

// test.c
/* This is a multi-line comment.
 * It spans multiple lines.
 */
int main() { // This is a single-line comment
    int a = 10;
    /* Another
     * comment */
    if (a > 5 && a != 100) {
        return a + 1; // Return value
    } else {
        while (a <= 5) {
            a++; // Increment a
        }
    }
    char *str = "Hello, C compiler! \\n"; // String literal with escape sequence
    printf("Value of a: %d\n", a); // A common function call
    return 0;
}

编译和运行
  1. 保存文件:将上述三个代码块分别保存为 lexer.h, lexer.c, main.ctest.c

  2. 编译:在终端中使用GCC编译(确保你安装了GCC):

    gcc -o mylexer main.c lexer.c -Wall -Wextra -std=c99
    
    
    • -o mylexer: 指定输出的可执行文件名为 mylexer

    • main.c lexer.c: 要编译的源文件。

    • -Wall -Wextra: 开启所有常见和额外的警告,帮助我们发现潜在问题。

    • -std=c99: 使用C99标准编译,确保 stdbool.h 等特性可用。

  3. 运行

    ./mylexer test.c
    
    

你将看到你的词法分析器将 test.c 中的代码分解成了一串串Token!

原理分析与逻辑剖析:词法分析器是如何“思考”的?

咱们的词法分析器,虽然简陋,但它已经包含了现代词法分析器的核心思想。

1. 字符流的线性扫描

词法分析器的工作方式是从左到右,一个字符一个字符地扫描源代码。advance() 函数是这个过程的核心,它负责读取下一个字符并更新当前位置(行号和列号),这是实现错误报告的关键。peek() 函数则允许我们“预读”一个字符,这对于识别多字符的Token(如 ==, /*)至关重要。

2. “跳过”无关紧要的字符

在C语言中,空格、制表符、换行符以及注释,它们的存在只是为了让代码更易读,对程序的实际执行逻辑没有影响。因此,词法分析器在识别Token之前,会先调用 skip_whitespace_and_comments()跳过这些“噪音”。

  • 空白字符is_whitespace() 函数判断,直接 advance() 跳过。

  • 单行注释 //:当遇到 / 且下一个字符是 / 时,就知道这是单行注释,然后一直 advance() 直到遇到换行符或文件结束。

  • 多行注释 /* ... */:当遇到 / 且下一个字符是 * 时,就知道是多行注释。然后就进入一个循环,不断 advance(),直到找到 */ 的组合。这里有一个重要的错误处理点:如果文件结束了还没找到 */,说明注释未闭合,这是语法错误,需要报告。

3. 模式匹配与最长匹配原则

词法分析器的核心就是模式匹配。它会根据当前字符,尝试匹配最长的可能Token。

  • 标识符与关键字

    • current_char_g 是字母或下划线时,词法分析器知道它可能是一个标识符或关键字。

    • 它会不断读取后续的字母、数字或下划线,直到遇到一个不符合规则的字符。

    • 例如,如果源代码是 int main

      • 读到 i,进入“标识符/关键字”模式。

      • 读到 n,继续。

      • 读到 t,继续。

      • 读到空格,停止。此时,它得到了字符串 int

    • 然后,它会拿着这个完整的字符串 int关键字表 keywords 中查找。如果找到了,就识别为 TOKEN_KEYWORD_INT;如果没找到,就识别为 TOKEN_IDENTIFIER

    • 最长匹配原则:例如,如果有 int_var 这样的变量名,它会完整识别为 int_var,而不是先识别 int 再识别 _var

  • 数字常量

    • current_char_g 是数字时,词法分析器会不断读取后续的数字,直到遇到非数字字符。

    • 例如,123a,它会识别出 123 是一个 TOKEN_INT_LITERAL,然后 a 会在下一次 lexer_getToken() 调用时被处理。

  • 字符串常量

    • 当遇到双引号 " 时,词法分析器知道这是一个字符串常量。

    • 它会一直读取字符,直到遇到下一个双引号。

    • 转义字符:如果遇到反斜杠 \,它会特殊处理,比如 \n 会被识别为换行符,但在这里我们只是简单地将 \ 和后面的字符一起存储到Token值中,具体的转义处理通常在语义分析或代码生成阶段进行。

    • 未闭合的字符串:如果直到文件结束都没有找到闭合的双引号,这是一个错误,需要报告。

  • 运算符和分隔符

    • 这些通常是单字符或双字符的。

    • 对于单字符的,直接识别并返回。

    • 对于双字符的,需要使用 peek() 来“偷看”下一个字符。例如,遇到 =,如果下一个字符也是 =,那就是 ==;否则就是 =。这种贪心匹配的策略,确保了 == 不会被错误地识别为两个 =

4. Token的创建与内存管理

每次成功识别一个Token,create_token() 函数就会被调用,它会动态分配内存来存储 Token 结构体和它的字符串值(词素)。由于是动态分配的,所以调用者(main 函数)必须负责在Token使用完毕后调用 free_token() 来释放内存,避免内存泄漏。

5. 错误处理

虽然我们的词法分析器很简单,但它已经包含了基本的错误处理:

  • 未闭合的注释:多行注释 /* ... 如果没有 */ 闭合,会报告错误。

  • 未闭合的字符串:字符串 " 如果没有 " 闭合,会报告错误。

  • 未知字符:如果遇到任何无法识别的字符(既不是空白、注释,也不是已知Token的开头),会报告“Unexpected character”错误。

这些错误报告对于后续的编译阶段(特别是语法分析)非常重要,因为词法错误会导致后续阶段无法正确解析。

词法分析器的工作流程(思维导图概念)

(这里想象一个流程图,描述 lexer_getToken() 的内部逻辑:

开始 -> 循环 (直到文件结束) -> 跳过空白和注释 -> 如果文件结束,返回 EOF Token -> 获取当前Token的起始行/列 -> 如果当前字符是字母或下划线? -> 是 -> 扫描标识符/关键字 -> 返回 Token -> 否 -> 如果当前字符是数字? -> 是 -> 扫描数字常量 -> 返回 Token -> 否 -> 如果当前字符是 "? -> 是 -> 扫描字符串常量 -> 返回 Token -> 否 -> 根据当前字符,使用 switch 判断: Case '+': 检查下一个字符是否为 '+' 或 '=',返回 ++, +=+ Case '-': 检查下一个字符是否为 '-', '=', '>',返回 --, -=, ->- ... (其他运算符和分隔符的类似逻辑) ... Default: 报告未知字符错误,返回 UNKNOWN Token 循环结束 -> 返回 EOF Token )

这个流程图清晰地展示了 lexer_getToken() 函数的决策过程,它如何根据当前字符的类型,选择不同的扫描逻辑,并最终返回一个Token。

总结与展望:你已经迈出了编译器的第一步!

恭喜你,老铁!你已经成功迈出了“手撸C语言编译器”的第一步,亲手实现了一个简易但功能完备的词法分析器

你现在应该明白:

  • 词法分析器就像编译器的“眼睛”,它把源代码这堆“乱码”分解成一个个有意义的“单词”(Token)。

  • 它通过线性扫描、模式匹配(有限状态机思想)和最长匹配原则来工作。

  • 它会无情地抛弃空白字符和注释,只保留有用的信息。

虽然这只是编译器的第一步,但它却是构建后续所有复杂功能的基础。没有它,语法分析器就无法理解代码的结构,语义分析器就无法检查代码的含义,更别提生成机器码了!

你现在已经彻底掀开了C语言编译器的第一层“底裤”,是不是感觉C语言的“红尘”也没那么神秘了?

下一篇文章,我们将进入编译器的第二阶段——语法分析!我们将学习如何把这些零散的Token,按照C语言的语法规则,组织成一棵有层次、有结构的“语法树”。那才是真正让编译器“理解”C语言代码结构的关键!

敬请期待!如果你觉得这篇文章对你有帮助,请务必点赞、收藏、转发,让更多想搞懂C语言底层原理的兄弟们看到!你的支持是我继续创作的最大动力!

咱们下期不见不散!






------------------------------------------------------------------------------------更新于2025.6.2 晚8:19

彻底掀开C语言的“底裤”:手撸编译器系列(二)—— 语法分析的乾坤大挪移!

引言:从“单词”到“句子”——编译器的“阅读理解”能力!

兄弟们,上回咱们聊了词法分析,把C代码从一串字符流变成了有意义的“词法单元”(Token)流。就像你学英语,先得认识 appleeatI 这些单词。

但光认识单词没用啊!你得能把它们组成有意义的句子,比如 I eat apple.,而不是 apple I eat. 或者 eat I apple.

编译器的“阅读理解”能力,就体现在它的第二个核心阶段——语法分析(Syntax Analysis),也叫解析(Parsing)

词法分析器吐出来的Token流,就像一堆散落在地上的积木块。语法分析器的任务,就是根据C语言的“积木搭建规则”(语法规则),把这些积木块严丝合缝地拼起来,形成一个完整的、有逻辑的“积木结构”——抽象语法树(Abstract Syntax Tree, AST)

你可能会问:

  • 为什么需要语法分析?词法分析不是已经把代码拆解了吗?

  • 什么是抽象语法树?它长啥样?有什么用?

  • 语法分析器是怎么知道哪些Token能组成合法“句子”的?

今天,咱们就来彻底搞懂这些问题,并且,咱们还要亲手“手撸”一个简易的C语言语法分析器,让它把咱们的C代码变成一棵“看得见、摸得着”的语法树!

语法分析:编译器的“大脑”——构建代码的“骨架”!

语法分析器的核心任务,就是根据编程语言的语法规则(Grammar Rules),验证输入的Token流是否合法,并将其组织成一个层次化的结构。这个结构,就是AST。

为什么需要语法分析?

词法分析只关心“单词”本身是否合法,比如 int 是一个关键字,main 是一个标识符,123 是一个整数。但它不关心这些“单词”组合起来是否符合C语言的“文法”。

例如,以下两行代码:

  1. int a = 10;

  2. int = 10 a;

对于词法分析器来说,它们都能分解出 int, =, 10, a, ; 这些Token。但很明显,第二行代码是语法错误的!它不符合C语言的声明语句规则。

这就是语法分析器要干的活儿:检查Token序列是否符合语言的语法规范。 如果不符合,就报告语法错误。如果符合,就构建AST。

抽象语法树(Abstract Syntax Tree, AST):代码的“骨架”

AST 是源代码的抽象语法结构的树状表示。树中的每个节点都表示源代码中的一个构造。

(这里想象一个简单的AST图: C代码:int a = 1 + 2; AST结构: FunctionDeclaration (main) -> VariableDeclaration (type: int, name: a) -> AssignmentExpression (=) -> BinaryExpression (+) -> IntegerLiteral (1) -> IntegerLiteral (2) )

AST的特点:

  • 抽象:它移除了语法细节,比如括号、分号等,只保留了程序的核心结构和语义信息。例如,a = 1 + 2;a = (1 + 2); 在AST中可能表示为相同的结构,因为它们的语义相同。

  • 层次化:它清晰地表示了代码的嵌套关系和操作符的优先级。

  • 通用:AST是一种中间表示,它独立于具体的源代码语言,也独立于目标机器。这使得编译器的后续阶段(语义分析、代码优化、代码生成)可以基于AST进行操作,而不需要直接处理原始源代码。

AST是编译器的“骨架”,后续的语义分析会在这棵骨架上填充“血肉”(类型信息、符号表等),代码生成器则会根据这棵骨架“打印”出机器指令。

语法分析器的工作原理:递归下降解析(Recursive Descent Parsing)

实现语法分析器有很多种方法,比如LL解析、LR解析等。对于咱们手撸一个简易编译器来说,**递归下降解析(Recursive Descent Parsing)**是最直观、最容易理解和实现的一种方法。

递归下降解析的核心思想

它的核心思想是:为语法规则中的每一个非终结符(Non-terminal,比如 函数声明语句表达式)编写一个对应的函数。

这些函数会:

  1. 匹配(Match) 预期的Token。

  2. 递归调用 其他函数来解析子结构。

  3. 构建 AST节点。

(这里想象一个流程图: parse_program() -> parse_function_declaration() -> parse_type_specifier() parse_identifier() match(TOKEN_LPAREN) match(TOKEN_RPAREN) parse_compound_statement() -> match(TOKEN_LBRACE) parse_statement() (loop) -> parse_return_statement() -> match(TOKEN_KEYWORD_RETURN) parse_expression() -> parse_term() -> parse_factor() -> match(TOKEN_INT_LITERAL) match(TOKEN_SEMICOLON) match(TOKEN_RBRACE)

语法规则(EBNF表示)

为了让我们的语法分析器有章可循,咱们先定义一个简化的C语言子集的语法规则,用**扩展巴科斯范式(Extended Backus-Naur Form, EBNF)**来表示。

  • program ::= function_declaration*

    • 一个程序由零个或多个函数声明组成。

  • function_declaration ::= type_specifier identifier '(' ')' compound_statement

    • 函数声明由类型说明符、标识符、一对括号和复合语句组成。

  • type_specifier ::= 'int' | 'void'

    • 类型说明符目前只支持 intvoid

  • compound_statement ::= '{' statement* '}'

    • 复合语句(代码块)由一对大括号和零个或多个语句组成。

  • statement ::= declaration_statement | expression_statement | return_statement | if_statement | while_statement | compound_statement

    • 一个语句可以是声明语句、表达式语句、返回语句、if语句、while语句或复合语句。

  • declaration_statement ::= type_specifier identifier ('=' expression)? ';'

    • 声明语句由类型说明符、标识符、可选的赋值表达式和分号组成。

  • expression_statement ::= (expression)? ';'

    • 表达式语句由可选的表达式和分号组成(例如 a++;; 空语句)。

  • return_statement ::= 'return' expression? ';'

    • 返回语句由 return 关键字、可选的表达式和分号组成。

  • if_statement ::= 'if' '(' expression ')' statement ('else' statement)?

    • if 语句由 if 关键字、括号内的表达式、一个语句和可选的 else 子句组成。

  • while_statement ::= 'while' '(' expression ')' statement

    • while 语句由 while 关键字、括号内的表达式和一个语句组成。

  • expression ::= assignment_expression

    • 表达式可以是一个赋值表达式。

  • assignment_expression ::= logical_or_expression (('=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') assignment_expression)?

    • 赋值表达式支持各种赋值运算符。

  • logical_or_expression ::= logical_and_expression ('||' logical_or_expression)?

    • 逻辑或表达式。

  • logical_and_expression ::= equality_expression ('&&' logical_and_expression)?

    • 逻辑与表达式。

  • equality_expression ::= relational_expression (('==' | '!=') equality_expression)?

    • 相等性表达式。

  • relational_expression ::= shift_expression (('<' | '>' | '<=' | '>=') relational_expression)?

    • 关系表达式。

  • shift_expression ::= additive_expression (('<<' | '>>') shift_expression)?

    • 移位表达式。

  • additive_expression ::= multiplicative_expression (('+' | '-') additive_expression)?

    • 加减法表达式。

  • multiplicative_expression ::= unary_expression (('*' | '/' | '%') multiplicative_expression)?

    • 乘除模表达式。

  • unary_expression ::= ('+' | '-' | '!' | '~' | '++' | '--' | '&' | '*')? primary_expression

    • 一元表达式,支持正负号、逻辑非、位非、自增、自减、取地址、解引用。

  • primary_expression ::= INT_LITERAL | STRING_LITERAL | IDENTIFIER | '(' expression ')'

    • 基本表达式,可以是整数常量、字符串常量、标识符或括号括起来的表达式。

这个语法规则看起来有点复杂,但别怕!它遵循了运算符的优先级和结合性。在递归下降解析中,优先级高的运算符在更深层的函数中解析,结合性则通过递归调用来实现。

抽象语法树(AST)的数据结构定义

为了构建AST,我们需要定义一系列C语言的结构体来表示不同类型的AST节点。

#ifndef AST_H
#define AST_H

#include <stdlib.h> // For malloc, free
#include <stdio.h>  // For printf, fprintf
#include <string.h> // For strdup

// AST节点类型枚举
typedef enum {
    AST_PROGRAM,                // 整个程序
    AST_FUNCTION_DECLARATION,   // 函数声明
    AST_COMPOUND_STATEMENT,     // 复合语句(代码块)
    AST_VARIABLE_DECLARATION,   // 变量声明
    AST_RETURN_STATEMENT,       // return 语句
    AST_IF_STATEMENT,           // if 语句
    AST_WHILE_STATEMENT,        // while 语句
    AST_EXPRESSION_STATEMENT,   // 表达式语句 (e.g., a = 10; a++;)
    AST_BINARY_EXPRESSION,      // 二元表达式 (e.g., a + b, x == y)
    AST_UNARY_EXPRESSION,       // 一元表达式 (e.g., -a, !b, ++c)
    AST_ASSIGNMENT_EXPRESSION,  // 赋值表达式 (e.g., a = b, x += y)
    AST_IDENTIFIER,             // 标识符 (变量名, 函数名)
    AST_INT_LITERAL,            // 整数常量
    AST_STRING_LITERAL,         // 字符串常量
    // AST_CALL_EXPRESSION,     // 函数调用表达式 (后续扩展)
    // AST_TYPE_SPECIFIER,      // 类型说明符 (int, void等,作为节点属性而非独立节点)
} ASTNodeType;

// 表达式操作符类型枚举 (用于二元和一元表达式)
typedef enum {
    OP_NONE,

    // 二元运算符
    OP_ADD,       // +
    OP_SUB,       // -
    OP_MUL,       // *
    OP_DIV,       // /
    OP_MOD,       // %
    OP_EQ,        // ==
    OP_NE,        // !=
    OP_LT,        // <
    OP_GT,        // >
    OP_LE,        // <=
    OP_GE,        // >=
    OP_AND,       // &&
    OP_OR,        // ||
    OP_BIT_AND,   // &
    OP_BIT_OR,    // |
    OP_BIT_XOR,   // ^
    OP_LSHIFT,    // <<
    OP_RSHIFT,    // >>

    // 赋值运算符
    OP_ASSIGN,        // =
    OP_ADD_ASSIGN,    // +=
    OP_SUB_ASSIGN,    // -=
    OP_MUL_ASSIGN,    // *=
    OP_DIV_ASSIGN,    // /=
    OP_MOD_ASSIGN,    // %=
    OP_LSHIFT_ASSIGN, // <<=
    OP_RSHIFT_ASSIGN, // >>=
    OP_BIT_AND_ASSIGN,// &=
    OP_BIT_OR_ASSIGN, // |=
    OP_BIT_XOR_ASSIGN,// ^=

    // 一元运算符
    OP_NEGATE,    // - (负号)
    OP_NOT,       // ! (逻辑非)
    OP_BIT_NOT,   // ~ (位非)
    OP_INCREMENT, // ++ (自增)
    OP_DECREMENT, // -- (自减)
    OP_ADDRESS_OF,// & (取地址)
    OP_DEREFERENCE// * (解引用)
} OperatorType;

// 前向声明所有AST节点类型,以便它们可以相互引用
struct ASTNode;
struct ASTProgram;
struct ASTFunctionDeclaration;
struct ASTCompoundStatement;
struct ASTVariableDeclaration;
struct ASTReturnStatement;
struct ASTIfStatement;
struct ASTWhileStatement;
struct ASTExpressionStatement;
struct ASTBinaryExpression;
struct ASTUnaryExpression;
struct ASTAssignmentExpression;
struct ASTIdentifier;
struct ASTIntLiteral;
struct ASTStringLiteral;

// AST节点基类(所有具体AST节点的通用部分)
// 这是一个联合体,可以存储不同类型的AST节点数据
typedef struct ASTNode {
    ASTNodeType type; // 节点的类型

    // 联合体,根据type字段存储不同的节点数据
    union {
        struct ASTProgram *program;
        struct ASTFunctionDeclaration *func_decl;
        struct ASTCompoundStatement *compound_stmt;
        struct ASTVariableDeclaration *var_decl;
        struct ASTReturnStatement *return_stmt;
        struct ASTIfStatement *if_stmt;
        struct ASTWhileStatement *while_stmt;
        struct ASTExpressionStatement *expr_stmt;
        struct ASTBinaryExpression *binary_expr;
        struct ASTUnaryExpression *unary_expr;
        struct ASTAssignmentExpression *assign_expr;
        struct ASTIdentifier *identifier;
        struct ASTIntLiteral *int_literal;
        struct ASTStringLiteral *string_literal;
    } data;
} ASTNode;

// ASTProgram: 整个程序的根节点
typedef struct ASTProgram {
    ASTNode **function_declarations; // 函数声明列表
    int num_function_declarations;   // 函数声明数量
    int capacity_function_declarations; // 列表容量
} ASTProgram;

// ASTFunctionDeclaration: 函数声明节点
typedef struct ASTFunctionDeclaration {
    char *return_type; // 返回类型 (e.g., "int", "void")
    char *name;        // 函数名
    // 参数列表 (暂时不处理,后续扩展)
    ASTNode *body;     // 函数体 (通常是一个复合语句AST_COMPOUND_STATEMENT)
} ASTFunctionDeclaration;

// ASTCompoundStatement: 复合语句节点 (代码块)
typedef struct ASTCompoundStatement {
    ASTNode **statements; // 语句列表
    int num_statements;   // 语句数量
    int capacity_statements; // 列表容量
} ASTCompoundStatement;

// ASTVariableDeclaration: 变量声明节点
typedef struct ASTVariableDeclaration {
    char *type;        // 变量类型 (e.g., "int")
    char *name;        // 变量名
    ASTNode *initializer; // 初始化表达式 (可选,如果声明时有赋值)
} ASTVariableDeclaration;

// ASTReturnStatement: return 语句节点
typedef struct ASTReturnStatement {
    ASTNode *expression; // return 的表达式 (可选)
} ASTReturnStatement;

// ASTIfStatement: if 语句节点
typedef struct ASTIfStatement {
    ASTNode *condition; // 条件表达式
    ASTNode *then_statement; // if 为真时执行的语句
    ASTNode *else_statement; // else 分支语句 (可选)
} ASTIfStatement;

// ASTWhileStatement: while 语句节点
typedef struct ASTWhileStatement {
    ASTNode *condition; // 循环条件表达式
    ASTNode *body;      // 循环体语句
} ASTWhileStatement;

// ASTExpressionStatement: 表达式语句节点 (如 `a = 10;` 或 `func();`)
typedef struct ASTExpressionStatement {
    ASTNode *expression; // 表达式
} ASTExpressionStatement;

// ASTBinaryExpression: 二元表达式节点 (a + b, x == y)
typedef struct ASTBinaryExpression {
    OperatorType op;    // 运算符类型
    ASTNode *left;      // 左操作数
    ASTNode *right;     // 右操作数
} ASTBinaryExpression;

// ASTUnaryExpression: 一元表达式节点 (-a, !b)
typedef struct ASTUnaryExpression {
    OperatorType op;    // 运算符类型
    ASTNode *operand;   // 操作数
} ASTUnaryExpression;

// ASTAssignmentExpression: 赋值表达式节点 (a = b, x += y)
typedef struct ASTAssignmentExpression {
    OperatorType op;    // 赋值运算符类型 (e.g., OP_ASSIGN, OP_ADD_ASSIGN)
    ASTNode *left;      // 左操作数 (通常是标识符)
    ASTNode *right;     // 右操作数 (赋值的值)
} ASTAssignmentExpression;

// ASTIdentifier: 标识符节点
typedef struct ASTIdentifier {
    char *name; // 标识符的名称
} ASTIdentifier;

// ASTIntLiteral: 整数常量节点
typedef struct ASTIntLiteral {
    int value; // 整数值
} ASTIntLiteral;

// ASTStringLiteral: 字符串常量节点
typedef struct ASTStringLiteral {
    char *value; // 字符串值
} ASTStringLiteral;

// -----------------------------------------------------------------------------
// AST节点创建辅助函数
// -----------------------------------------------------------------------------

// 创建一个通用的ASTNode节点,并初始化其类型
ASTNode *create_ast_node(ASTNodeType type);

// 创建具体的AST节点并填充数据
ASTNode *create_ast_program();
ASTNode *create_ast_function_declaration(const char *return_type, const char *name, ASTNode *body);
ASTNode *create_ast_compound_statement();
ASTNode *create_ast_variable_declaration(const char *type, const char *name, ASTNode *initializer);
ASTNode *create_ast_return_statement(ASTNode *expression);
ASTNode *create_ast_if_statement(ASTNode *condition, ASTNode *then_stmt, ASTNode *else_stmt);
ASTNode *create_ast_while_statement(ASTNode *condition, ASTNode *body);
ASTNode *create_ast_expression_statement(ASTNode *expression);
ASTNode *create_ast_binary_expression(OperatorType op, ASTNode *left, ASTNode *right);
ASTNode *create_ast_unary_expression(OperatorType op, ASTNode *operand);
ASTNode *create_ast_assignment_expression(OperatorType op, ASTNode *left, ASTNode *right);
ASTNode *create_ast_identifier(const char *name);
ASTNode *create_ast_int_literal(int value);
ASTNode *create_ast_string_literal(const char *value);

// 向ASTProgram中添加函数声明
void ast_program_add_function_declaration(ASTProgram *program, ASTNode *func_decl_node);
// 向ASTCompoundStatement中添加语句
void ast_compound_statement_add_statement(ASTCompoundStatement *compound_stmt, ASTNode *statement_node);

// -----------------------------------------------------------------------------
// AST节点内存释放函数
// -----------------------------------------------------------------------------

// 递归释放AST节点的内存
void free_ast_node(ASTNode *node);

#endif // AST_H

ast.c:AST数据结构的实现
#include "ast.h"
#include <stdio.h> // For fprintf

// -----------------------------------------------------------------------------
// AST节点创建辅助函数的实现
// -----------------------------------------------------------------------------

// 创建一个通用的ASTNode节点,并初始化其类型
ASTNode *create_ast_node(ASTNodeType type) {
    ASTNode *node = (ASTNode *)malloc(sizeof(ASTNode));
    if (!node) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTNode.\n");
        exit(EXIT_FAILURE);
    }
    node->type = type;
    // 初始化联合体内容为NULL或0,避免野指针
    memset(&node->data, 0, sizeof(node->data));
    return node;
}

// 创建ASTProgram节点
ASTNode *create_ast_program() {
    ASTNode *node = create_ast_node(AST_PROGRAM);
    node->data.program = (ASTProgram *)malloc(sizeof(ASTProgram));
    if (!node->data.program) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTProgram.\n");
        exit(EXIT_FAILURE);
    }
    node->data.program->function_declarations = NULL;
    node->data.program->num_function_declarations = 0;
    node->data.program->capacity_function_declarations = 0;
    return node;
}

// 创建ASTFunctionDeclaration节点
ASTNode *create_ast_function_declaration(const char *return_type, const char *name, ASTNode *body) {
    ASTNode *node = create_ast_node(AST_FUNCTION_DECLARATION);
    node->data.func_decl = (ASTFunctionDeclaration *)malloc(sizeof(ASTFunctionDeclaration));
    if (!node->data.func_decl) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTFunctionDeclaration.\n");
        exit(EXIT_FAILURE);
    }
    node->data.func_decl->return_type = strdup(return_type);
    node->data.func_decl->name = strdup(name);
    node->data.func_decl->body = body;
    return node;
}

// 创建ASTCompoundStatement节点
ASTNode *create_ast_compound_statement() {
    ASTNode *node = create_ast_node(AST_COMPOUND_STATEMENT);
    node->data.compound_stmt = (ASTCompoundStatement *)malloc(sizeof(ASTCompoundStatement));
    if (!node->data.compound_stmt) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTCompoundStatement.\n");
        exit(EXIT_FAILURE);
    }
    node->data.compound_stmt->statements = NULL;
    node->data.compound_stmt->num_statements = 0;
    node->data.compound_stmt->capacity_statements = 0;
    return node;
}

// 创建ASTVariableDeclaration节点
ASTNode *create_ast_variable_declaration(const char *type, const char *name, ASTNode *initializer) {
    ASTNode *node = create_ast_node(AST_VARIABLE_DECLARATION);
    node->data.var_decl = (ASTVariableDeclaration *)malloc(sizeof(ASTVariableDeclaration));
    if (!node->data.var_decl) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTVariableDeclaration.\n");
        exit(EXIT_FAILURE);
    }
    node->data.var_decl->type = strdup(type);
    node->data.var_decl->name = strdup(name);
    node->data.var_decl->initializer = initializer;
    return node;
}

// 创建ASTReturnStatement节点
ASTNode *create_ast_return_statement(ASTNode *expression) {
    ASTNode *node = create_ast_node(AST_RETURN_STATEMENT);
    node->data.return_stmt = (ASTReturnStatement *)malloc(sizeof(ASTReturnStatement));
    if (!node->data.return_stmt) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTReturnStatement.\n");
        exit(EXIT_FAILURE);
    }
    node->data.return_stmt->expression = expression;
    return node;
}

// 创建ASTIfStatement节点
ASTNode *create_ast_if_statement(ASTNode *condition, ASTNode *then_stmt, ASTNode *else_stmt) {
    ASTNode *node = create_ast_node(AST_IF_STATEMENT);
    node->data.if_stmt = (ASTIfStatement *)malloc(sizeof(ASTIfStatement));
    if (!node->data.if_stmt) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTIfStatement.\n");
        exit(EXIT_FAILURE);
    }
    node->data.if_stmt->condition = condition;
    node->data.if_stmt->then_statement = then_stmt;
    node->data.if_stmt->else_statement = else_stmt;
    return node;
}

// 创建ASTWhileStatement节点
ASTNode *create_ast_while_statement(ASTNode *condition, ASTNode *body) {
    ASTNode *node = create_ast_node(AST_WHILE_STATEMENT);
    node->data.while_stmt = (ASTWhileStatement *)malloc(sizeof(ASTWhileStatement));
    if (!node->data.while_stmt) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTWhileStatement.\n");
        exit(EXIT_FAILURE);
    }
    node->data.while_stmt->condition = condition;
    node->data.while_stmt->body = body;
    return node;
}

// 创建ASTExpressionStatement节点
ASTNode *create_ast_expression_statement(ASTNode *expression) {
    ASTNode *node = create_ast_node(AST_EXPRESSION_STATEMENT);
    node->data.expr_stmt = (ASTExpressionStatement *)malloc(sizeof(ASTExpressionStatement));
    if (!node->data.expr_stmt) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTExpressionStatement.\n");
        exit(EXIT_FAILURE);
    }
    node->data.expr_stmt->expression = expression;
    return node;
}

// 创建ASTBinaryExpression节点
ASTNode *create_ast_binary_expression(OperatorType op, ASTNode *left, ASTNode *right) {
    ASTNode *node = create_ast_node(AST_BINARY_EXPRESSION);
    node->data.binary_expr = (ASTBinaryExpression *)malloc(sizeof(ASTBinaryExpression));
    if (!node->data.binary_expr) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTBinaryExpression.\n");
        exit(EXIT_FAILURE);
    }
    node->data.binary_expr->op = op;
    node->data.binary_expr->left = left;
    node->data.binary_expr->right = right;
    return node;
}

// 创建ASTUnaryExpression节点
ASTNode *create_ast_unary_expression(OperatorType op, ASTNode *operand) {
    ASTNode *node = create_ast_node(AST_UNARY_EXPRESSION);
    node->data.unary_expr = (ASTUnaryExpression *)malloc(sizeof(ASTUnaryExpression));
    if (!node->data.unary_expr) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTUnaryExpression.\n");
        exit(EXIT_FAILURE);
    }
    node->data.unary_expr->op = op;
    node->data.unary_expr->operand = operand;
    return node;
}

// 创建ASTAssignmentExpression节点
ASTNode *create_ast_assignment_expression(OperatorType op, ASTNode *left, ASTNode *right) {
    ASTNode *node = create_ast_node(AST_ASSIGNMENT_EXPRESSION);
    node->data.assign_expr = (ASTAssignmentExpression *)malloc(sizeof(ASTAssignmentExpression));
    if (!node->data.assign_expr) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTAssignmentExpression.\n");
        exit(EXIT_FAILURE);
    }
    node->data.assign_expr->op = op;
    node->data.assign_expr->left = left;
    node->data.assign_expr->right = right;
    return node;
}

// 创建ASTIdentifier节点
ASTNode *create_ast_identifier(const char *name) {
    ASTNode *node = create_ast_node(AST_IDENTIFIER);
    node->data.identifier = (ASTIdentifier *)malloc(sizeof(ASTIdentifier));
    if (!node->data.identifier) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTIdentifier.\n");
        exit(EXIT_FAILURE);
    }
    node->data.identifier->name = strdup(name);
    return node;
}

// 创建ASTIntLiteral节点
ASTNode *create_ast_int_literal(int value) {
    ASTNode *node = create_ast_node(AST_INT_LITERAL);
    node->data.int_literal = (ASTIntLiteral *)malloc(sizeof(ASTIntLiteral));
    if (!node->data.int_literal) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTIntLiteral.\n");
        exit(EXIT_FAILURE);
    }
    node->data.int_literal->value = value;
    return node;
}

// 创建ASTStringLiteral节点
ASTNode *create_ast_string_literal(const char *value) {
    ASTNode *node = create_ast_node(AST_STRING_LITERAL);
    node->data.string_literal = (ASTStringLiteral *)malloc(sizeof(ASTStringLiteral));
    if (!node->data.string_literal) {
        fprintf(stderr, "Error: Failed to allocate memory for ASTStringLiteral.\n");
        exit(EXIT_FAILURE);
    }
    node->data.string_literal->value = strdup(value);
    return node;
}

// 向ASTProgram中添加函数声明
void ast_program_add_function_declaration(ASTProgram *program, ASTNode *func_decl_node) {
    if (program->num_function_declarations >= program->capacity_function_declarations) {
        // 扩容
        int new_capacity = program->capacity_function_declarations == 0 ? 4 : program->capacity_function_declarations * 2;
        ASTNode **new_declarations = (ASTNode **)realloc(program->function_declarations, new_capacity * sizeof(ASTNode *));
        if (!new_declarations) {
            fprintf(stderr, "Error: Failed to reallocate memory for function declarations.\n");
            exit(EXIT_FAILURE);
        }
        program->function_declarations = new_declarations;
        program->capacity_function_declarations = new_capacity;
    }
    program->function_declarations[program->num_function_declarations++] = func_decl_node;
}

// 向ASTCompoundStatement中添加语句
void ast_compound_statement_add_statement(ASTCompoundStatement *compound_stmt, ASTNode *statement_node) {
    if (compound_stmt->num_statements >= compound_stmt->capacity_statements) {
        // 扩容
        int new_capacity = compound_stmt->capacity_statements == 0 ? 8 : compound_stmt->capacity_statements * 2;
        ASTNode **new_statements = (ASTNode **)realloc(compound_stmt->statements, new_capacity * sizeof(ASTNode *));
        if (!new_statements) {
            fprintf(stderr, "Error: Failed to reallocate memory for statements.\n");
            exit(EXIT_FAILURE);
        }
        compound_stmt->statements = new_statements;
        compound_stmt->capacity_statements = new_capacity;
    }
    compound_stmt->statements[compound_stmt->num_statements++] = statement_node;
}

// -----------------------------------------------------------------------------
// AST节点内存释放函数的实现
// -----------------------------------------------------------------------------

// 递归释放AST节点的内存
void free_ast_node(ASTNode *node) {
    if (!node) {
        return;
    }

    switch (node->type) {
        case AST_PROGRAM:
            if (node->data.program) {
                for (int i = 0; i < node->data.program->num_function_declarations; ++i) {
                    free_ast_node(node->data.program->function_declarations[i]);
                }
                free(node->data.program->function_declarations);
                free(node->data.program);
            }
            break;
        case AST_FUNCTION_DECLARATION:
            if (node->data.func_decl) {
                free(node->data.func_decl->return_type);
                free(node->data.func_decl->name);
                free_ast_node(node->data.func_decl->body);
                free(node->data.func_decl);
            }
            break;
        case AST_COMPOUND_STATEMENT:
            if (node->data.compound_stmt) {
                for (int i = 0; i < node->data.compound_stmt->num_statements; ++i) {
                    free_ast_node(node->data.compound_stmt->statements[i]);
                }
                free(node->data.compound_stmt->statements);
                free(node->data.compound_stmt);
            }
            break;
        case AST_VARIABLE_DECLARATION:
            if (node->data.var_decl) {
                free(node->data.var_decl->type);
                free(node->data.var_decl->name);
                free_ast_node(node->data.var_decl->initializer);
                free(node->data.var_decl);
            }
            break;
        case AST_RETURN_STATEMENT:
            if (node->data.return_stmt) {
                free_ast_node(node->data.return_stmt->expression);
                free(node->data.return_stmt);
            }
            break;
        case AST_IF_STATEMENT:
            if (node->data.if_stmt) {
                free_ast_node(node->data.if_stmt->condition);
                free_ast_node(node->data.if_stmt->then_statement);
                free_ast_node(node->data.if_stmt->else_statement); // else_statement可能为NULL
                free(node->data.if_stmt);
            }
            break;
        case AST_WHILE_STATEMENT:
            if (node->data.while_stmt) {
                free_ast_node(node->data.while_stmt->condition);
                free_ast_node(node->data.while_stmt->body);
                free(node->data.while_stmt);
            }
            break;
        case AST_EXPRESSION_STATEMENT:
            if (node->data.expr_stmt) {
                free_ast_node(node->data.expr_stmt->expression);
                free(node->data.expr_stmt);
            }
            break;
        case AST_BINARY_EXPRESSION:
            if (node->data.binary_expr) {
                free_ast_node(node->data.binary_expr->left);
                free_ast_node(node->data.binary_expr->right);
                free(node->data.binary_expr);
            }
            break;
        case AST_UNARY_EXPRESSION:
            if (node->data.unary_expr) {
                free_ast_node(node->data.unary_expr->operand);
                free(node->data.unary_expr);
            }
            break;
        case AST_ASSIGNMENT_EXPRESSION:
            if (node->data.assign_expr) {
                free_ast_node(node->data.assign_expr->left);
                free_ast_node(node->data.assign_expr->right);
                free(node->data.assign_expr);
            }
            break;
        case AST_IDENTIFIER:
            if (node->data.identifier) {
                free(node->data.identifier->name);
                free(node->data.identifier);
            }
            break;
        case AST_INT_LITERAL:
            // 整数常量没有动态分配的字符串,直接释放结构体
            if (node->data.int_literal) {
                free(node->data.int_literal);
            }
            break;
        case AST_STRING_LITERAL:
            if (node->data.string_literal) {
                free(node->data.string_literal->value);
                free(node->data.string_literal);
            }
            break;
        default:
            // 未知类型,或者不需要特殊处理的类型
            break;
    }
    free(node); // 最后释放ASTNode本身
}


语法分析器实现:parser.hparser.c

现在,咱们开始编写语法分析器的核心逻辑。

parser.h:定义解析器接口
#ifndef PARSER_H
#define PARSER_H

#include "lexer.h" // 包含词法分析器头文件,因为解析器需要Token
#include "ast.h"   // 包含AST头文件,因为解析器需要构建AST

// 全局的当前Token指针
// parser_current_token_g: 当前正在处理的Token
extern Token *parser_current_token_g;

// 解析器初始化函数
// 参数:无
// 返回值:无
void parser_init();

// 解析整个程序
// 这是解析器的入口点,它会从Token流中构建整个程序的AST
// 返回值:指向ASTProgram根节点的指针
ASTNode *parse_program();

// 释放解析器资源(主要是当前Token)
void parser_cleanup();

// 辅助函数:将ASTNodeType转换为可读字符串(用于调试打印AST)
const char *ast_node_type_to_string(ASTNodeType type);

// 辅助函数:将OperatorType转换为可读字符串(用于调试打印AST)
const char *operator_type_to_string(OperatorType op);

// 辅助函数:打印AST(用于调试)
void print_ast(ASTNode *node, int indent);

#endif // PARSER_H

parser.c:语法分析器的核心实现
#include "parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// 全局的当前Token指针
Token *parser_current_token_g = NULL;

// -----------------------------------------------------------------------------
// 辅助函数:Token操作
// -----------------------------------------------------------------------------

// 获取下一个Token并更新全局当前Token
static void get_next_token() {
    // 如果当前Token不是NULL,先释放它,避免内存泄漏
    if (parser_current_token_g != NULL) {
        free_token(parser_current_token_g);
    }
    // 从词法分析器获取下一个Token
    parser_current_token_g = lexer_getToken();
}

// 检查当前Token的类型是否符合预期
// 参数:expected_type - 预期的Token类型
// 返回值:如果符合预期返回true,否则返回false
static bool check_token(TokenType expected_type) {
    return parser_current_token_g->type == expected_type;
}

// 匹配并消耗一个指定类型的Token
// 如果当前Token类型与expected_type匹配,则消耗该Token并获取下一个Token;
// 否则,报告语法错误并退出。
// 参数:expected_type - 预期的Token类型
// 返回值:无
static void match(TokenType expected_type) {
    if (!check_token(expected_type)) {
        fprintf(stderr, "Syntax Error: Expected token type %s, but got %s ('%s') at line %d, column %d.\n",
                token_type_to_string(expected_type),
                token_type_to_string(parser_current_token_g->type),
                parser_current_token_g->value ? parser_current_token_g->value : "",
                parser_current_token_g->line,
                parser_current_token_g->column);
        exit(EXIT_FAILURE); // 遇到语法错误,直接退出
    }
    // 匹配成功,获取下一个Token
    get_next_token();
}

// 报告语法错误并退出
static void parser_error(const char *message) {
    fprintf(stderr, "Syntax Error: %s at line %d, column %d (Token: '%s', Type: %s).\n",
            message,
            parser_current_token_g->line,
            parser_current_token_g->column,
            parser_current_token_g->value ? parser_current_token_g->value : "",
            token_type_to_string(parser_current_token_g->type));
    exit(EXIT_FAILURE);
}

// -----------------------------------------------------------------------------
// 递归下降解析函数 (对应EBNF语法规则)
// -----------------------------------------------------------------------------

// 前向声明所有解析函数,因为它们会相互递归调用
static ASTNode *parse_type_specifier();
static ASTNode *parse_expression();
static ASTNode *parse_assignment_expression();
static ASTNode *parse_logical_or_expression();
static ASTNode *parse_logical_and_expression();
static ASTNode *parse_equality_expression();
static ASTNode *parse_relational_expression();
static ASTNode *parse_shift_expression();
static ASTNode *parse_additive_expression();
static ASTNode *parse_multiplicative_expression();
static ASTNode *parse_unary_expression();
static ASTNode *parse_primary_expression();
static ASTNode *parse_statement();
static ASTNode *parse_declaration_statement();
static ASTNode *parse_expression_statement();
static ASTNode *parse_return_statement();
static ASTNode *parse_if_statement();
static ASTNode *parse_while_statement();
static ASTNode *parse_compound_statement();
static ASTNode *parse_function_declaration();

// 解析类型说明符 (int, void)
// type_specifier ::= 'int' | 'void'
static ASTNode *parse_type_specifier() {
    // 记录当前Token的词素,作为类型名
    char *type_name = NULL;
    if (check_token(TOKEN_KEYWORD_INT)) {
        type_name = strdup(parser_current_token_g->value);
        match(TOKEN_KEYWORD_INT);
    } else if (check_token(TOKEN_KEYWORD_VOID)) {
        type_name = strdup(parser_current_token_g->value);
        match(TOKEN_KEYWORD_VOID);
    } else {
        parser_error("Expected a type specifier (int or void).");
    }
    // 这里我们不创建独立的AST_TYPE_SPECIFIER节点,直接返回类型字符串
    // 并在需要的地方使用它,如AST_FUNCTION_DECLARATION和AST_VARIABLE_DECLARATION
    // 这样做是为了简化AST结构,因为类型更多是属性而非独立操作
    
    // 注意:这里返回的是一个字符串,而不是ASTNode。
    // 在实际使用时,需要将其作为ASTNode的属性,并负责其内存管理。
    // 为了与ASTNode*返回类型兼容,我们暂时返回一个ASTIdentifier节点来携带这个字符串
    // 但更规范的做法是让调用者直接获取这个字符串,或者定义一个AST_TYPE节点。
    // 这里为了简化演示,我们暂时用ASTIdentifier来“伪装”类型名,后续可优化。
    // 更好的做法是在需要类型字符串的地方直接返回char*,并由调用者负责strdup和free。
    // 为了保持ASTNode*的统一返回类型,我们创建一个临时的ASTIdentifier来承载类型名
    // 实际编译器中,类型信息会更复杂,通常会通过符号表和类型系统来管理。
    return create_ast_identifier(type_name); // 暂时用identifier节点来传递类型名
}


// 解析主表达式 (最高优先级,如字面量、标识符、括号表达式)
// primary_expression ::= INT_LITERAL | STRING_LITERAL | IDENTIFIER | '(' expression ')'
static ASTNode *parse_primary_expression() {
    ASTNode *node = NULL;
    if (check_token(TOKEN_INT_LITERAL)) {
        // 将字符串值转换为整数
        int value = atoi(parser_current_token_g->value);
        node = create_ast_int_literal(value);
        match(TOKEN_INT_LITERAL);
    } else if (check_token(TOKEN_STRING_LITERAL)) {
        node = create_ast_string_literal(parser_current_token_g->value);
        match(TOKEN_STRING_LITERAL);
    } else if (check_token(TOKEN_IDENTIFIER)) {
        node = create_ast_identifier(parser_current_token_g->value);
        match(TOKEN_IDENTIFIER);
    } else if (check_token(TOKEN_LPAREN)) {
        match(TOKEN_LPAREN);
        node = parse_expression(); // 递归解析括号内的表达式
        match(TOKEN_RPAREN);
    } else {
        parser_error("Expected an integer literal, string literal, identifier, or '(' for primary expression.");
    }
    return node;
}

// 解析一元表达式
// unary_expression ::= ('+' | '-' | '!' | '~' | '++' | '--' | '&' | '*')? primary_expression
static ASTNode *parse_unary_expression() {
    OperatorType op_type = OP_NONE;
    if (check_token(TOKEN_PLUS)) {
        op_type = OP_ADD; // 正号
        match(TOKEN_PLUS);
    } else if (check_token(TOKEN_MINUS)) {
        op_type = OP_NEGATE; // 负号
        match(TOKEN_MINUS);
    } else if (check_token(TOKEN_EXCLAMATION)) {
        op_type = OP_NOT; // 逻辑非
        match(TOKEN_EXCLAMATION);
    } else if (check_token(TOKEN_TILDE)) {
        op_type = OP_BIT_NOT; // 位非
        match(TOKEN_TILDE);
    } else if (check_token(TOKEN_INC)) {
        op_type = OP_INCREMENT; // 前置自增
        match(TOKEN_INC);
    } else if (check_token(TOKEN_DEC)) {
        op_type = OP_DECREMENT; // 前置自减
        match(TOKEN_DEC);
    } else if (check_token(TOKEN_AMPERSAND)) {
        op_type = OP_ADDRESS_OF; // 取地址
        match(TOKEN_AMPERSAND);
    } else if (check_token(TOKEN_STAR)) {
        op_type = OP_DEREFERENCE; // 解引用
        match(TOKEN_STAR);
    }

    ASTNode *operand = parse_primary_expression(); // 解析操作数

    if (op_type != OP_NONE) {
        return create_ast_unary_expression(op_type, operand);
    } else {
        return operand; // 如果没有一元运算符,直接返回主表达式
    }
}

// 解析乘除模表达式 (优先级高于加减)
// multiplicative_expression ::= unary_expression (('*' | '/' | '%') multiplicative_expression)?
static ASTNode *parse_multiplicative_expression() {
    ASTNode *node = parse_unary_expression(); // 先解析一元表达式

    while (check_token(TOKEN_STAR) || check_token(TOKEN_SLASH) || check_token(TOKEN_MODULO)) {
        OperatorType op_type;
        if (check_token(TOKEN_STAR)) {
            op_type = OP_MUL;
            match(TOKEN_STAR);
        } else if (check_token(TOKEN_SLASH)) {
            op_type = OP_DIV;
            match(TOKEN_SLASH);
        } else { // TOKEN_MODULO
            op_type = OP_MOD;
            match(TOKEN_MODULO);
        }
        // 递归解析右侧的乘除模表达式,实现左结合性
        node = create_ast_binary_expression(op_type, node, parse_unary_expression()); // 注意这里是parse_unary_expression
    }
    return node;
}

// 解析加减法表达式 (优先级低于乘除)
// additive_expression ::= multiplicative_expression (('+' | '-') additive_expression)?
static ASTNode *parse_additive_expression() {
    ASTNode *node = parse_multiplicative_expression(); // 先解析乘除模表达式

    while (check_token(TOKEN_PLUS) || check_token(TOKEN_MINUS)) {
        OperatorType op_type;
        if (check_token(TOKEN_PLUS)) {
            op_type = OP_ADD;
            match(TOKEN_PLUS);
        } else { // TOKEN_MINUS
            op_type = OP_SUB;
            match(TOKEN_MINUS);
        }
        // 递归解析右侧的加减法表达式,实现左结合性
        node = create_ast_binary_expression(op_type, node, parse_multiplicative_expression()); // 注意这里是parse_multiplicative_expression
    }
    return node;
}

// 解析移位表达式
// shift_expression ::= additive_expression (('<<' | '>>') shift_expression)?
static ASTNode *parse_shift_expression() {
    ASTNode *node = parse_additive_expression();

    while (check_token(TOKEN_LSHIFT) || check_token(TOKEN_RSHIFT)) {
        OperatorType op_type;
        if (check_token(TOKEN_LSHIFT)) {
            op_type = OP_LSHIFT;
            match(TOKEN_LSHIFT);
        } else { // TOKEN_RSHIFT
            op_type = OP_RSHIFT;
            match(TOKEN_RSHIFT);
        }
        node = create_ast_binary_expression(op_type, node, parse_additive_expression());
    }
    return node;
}

// 解析关系表达式 (<, >, <=, >=)
// relational_expression ::= shift_expression (('<' | '>' | '<=' | '>=') relational_expression)?
static ASTNode *parse_relational_expression() {
    ASTNode *node = parse_shift_expression();

    while (check_token(TOKEN_LT) || check_token(TOKEN_GT) || check_token(TOKEN_LE) || check_token(TOKEN_GE)) {
        OperatorType op_type;
        if (check_token(TOKEN_LT)) {
            op_type = OP_LT;
            match(TOKEN_LT);
        } else if (check_token(TOKEN_GT)) {
            op_type = OP_GT;
            match(TOKEN_GT);
        } else if (check_token(TOKEN_LE)) {
            op_type = OP_LE;
            match(TOKEN_LE);
        } else { // TOKEN_GE
            op_type = OP_GE;
            match(TOKEN_GE);
        }
        node = create_ast_binary_expression(op_type, node, parse_shift_expression());
    }
    return node;
}

// 解析相等性表达式 (==, !=)
// equality_expression ::= relational_expression (('==' | '!=') equality_expression)?
static ASTNode *parse_equality_expression() {
    ASTNode *node = parse_relational_expression();

    while (check_token(TOKEN_EQ) || check_token(TOKEN_NE)) {
        OperatorType op_type;
        if (check_token(TOKEN_EQ)) {
            op_type = OP_EQ;
            match(TOKEN_EQ);
        } else { // TOKEN_NE
            op_type = OP_NE;
            match(TOKEN_NE);
        }
        node = create_ast_binary_expression(op_type, node, parse_relational_expression());
    }
    return node;
}

// 解析逻辑与表达式 (&&)
// logical_and_expression ::= equality_expression ('&&' logical_and_expression)?
static ASTNode *parse_logical_and_expression() {
    ASTNode *node = parse_equality_expression();

    while (check_token(TOKEN_AND)) {
        match(TOKEN_AND);
        node = create_ast_binary_expression(OP_AND, node, parse_equality_expression());
    }
    return node;
}

// 解析逻辑或表达式 (||)
// logical_or_expression ::= logical_and_expression ('||' logical_or_expression)?
static ASTNode *parse_logical_or_expression() {
    ASTNode *node = parse_logical_and_expression();

    while (check_token(TOKEN_OR)) {
        match(TOKEN_OR);
        node = create_ast_binary_expression(OP_OR, node, parse_logical_and_expression());
    }
    return node;
}

// 解析赋值表达式 (优先级最低,右结合性)
// assignment_expression ::= logical_or_expression (('=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') assignment_expression)?
static ASTNode *parse_assignment_expression() {
    ASTNode *left_expr = parse_logical_or_expression();

    // 检查是否是赋值运算符
    if (check_token(TOKEN_ASSIGN) || check_token(TOKEN_PLUS_ASSIGN) || check_token(TOKEN_MINUS_ASSIGN) ||
        check_token(TOKEN_STAR_ASSIGN) || check_token(TOKEN_SLASH_ASSIGN) || check_token(TOKEN_MODULO_ASSIGN) ||
        check_token(TOKEN_AMPERSAND_ASSIGN) || check_token(TOKEN_PIPE_ASSIGN) || check_token(TOKEN_CARET_ASSIGN) ||
        check_token(TOKEN_LSHIFT_ASSIGN) || check_token(TOKEN_RSHIFT_ASSIGN)) {

        OperatorType op_type;
        TokenType current_token_type = parser_current_token_g->type;

        if (current_token_type == TOKEN_ASSIGN) op_type = OP_ASSIGN;
        else if (current_token_type == TOKEN_PLUS_ASSIGN) op_type = OP_ADD_ASSIGN;
        else if (current_token_type == TOKEN_MINUS_ASSIGN) op_type = OP_SUB_ASSIGN;
        else if (current_token_type == TOKEN_STAR_ASSIGN) op_type = OP_MUL_ASSIGN;
        else if (current_token_type == TOKEN_SLASH_ASSIGN) op_type = OP_DIV_ASSIGN;
        else if (current_token_type == TOKEN_MODULO_ASSIGN) op_type = OP_MOD_ASSIGN;
        else if (current_token_type == TOKEN_AMPERSAND_ASSIGN) op_type = OP_BIT_AND_ASSIGN;
        else if (current_token_type == TOKEN_PIPE_ASSIGN) op_type = OP_BIT_OR_ASSIGN;
        else if (current_token_type == TOKEN_CARET_ASSIGN) op_type = OP_BIT_XOR_ASSIGN;
        else if (current_token_type == TOKEN_LSHIFT_ASSIGN) op_type = OP_LSHIFT_ASSIGN;
        else /* if (current_token_type == TOKEN_RSHIFT_ASSIGN) */ op_type = OP_RSHIFT_ASSIGN;

        match(current_token_type); // 消耗赋值运算符

        ASTNode *right_expr = parse_assignment_expression(); // 递归解析右侧的赋值表达式,实现右结合性

        return create_ast_assignment_expression(op_type, left_expr, right_expr);
    }
    return left_expr; // 如果没有赋值运算符,则只是一个逻辑或表达式
}

// 解析表达式 (目前只处理赋值表达式,后续可扩展逗号表达式等)
// expression ::= assignment_expression
static ASTNode *parse_expression() {
    return parse_assignment_expression();
}

// 解析声明语句
// declaration_statement ::= type_specifier identifier ('=' expression)? ';'
static ASTNode *parse_declaration_statement() {
    // 获取类型说明符
    ASTNode *type_node = parse_type_specifier(); // 暂时返回ASTIdentifier来承载类型名
    char *type_name = strdup(type_node->data.identifier->name); // 复制类型名
    free_ast_node(type_node); // 释放临时类型节点

    // 获取标识符(变量名)
    if (!check_token(TOKEN_IDENTIFIER)) {
        parser_error("Expected an identifier after type specifier in declaration.");
    }
    char *var_name = strdup(parser_current_token_g->value);
    match(TOKEN_IDENTIFIER);

    ASTNode *initializer = NULL;
    if (check_token(TOKEN_ASSIGN)) { // 如果有赋值号,说明有初始化表达式
        match(TOKEN_ASSIGN);
        initializer = parse_expression(); // 解析初始化表达式
    }

    match(TOKEN_SEMICOLON); // 匹配分号

    return create_ast_variable_declaration(type_name, var_name, initializer);
}

// 解析表达式语句
// expression_statement ::= (expression)? ';'
static ASTNode *parse_expression_statement() {
    ASTNode *expr = NULL;
    if (!check_token(TOKEN_SEMICOLON)) { // 如果不是分号,说明有表达式
        expr = parse_expression();
    }
    match(TOKEN_SEMICOLON); // 匹配分号
    return create_ast_expression_statement(expr);
}

// 解析返回语句
// return_statement ::= 'return' expression? ';'
static ASTNode *parse_return_statement() {
    match(TOKEN_KEYWORD_RETURN); // 匹配 return 关键字
    ASTNode *expr = NULL;
    if (!check_token(TOKEN_SEMICOLON)) { // 如果不是分号,说明有返回表达式
        expr = parse_expression();
    }
    match(TOKEN_SEMICOLON); // 匹配分号
    return create_ast_return_statement(expr);
}

// 解析 if 语句
// if_statement ::= 'if' '(' expression ')' statement ('else' statement)?
static ASTNode *parse_if_statement() {
    match(TOKEN_KEYWORD_IF); // 匹配 if 关键字
    match(TOKEN_LPAREN);     // 匹配 '('
    ASTNode *condition = parse_expression(); // 解析条件表达式
    match(TOKEN_RPAREN);     // 匹配 ')'
    ASTNode *then_statement = parse_statement(); // 解析 if 为真时执行的语句

    ASTNode *else_statement = NULL;
    if (check_token(TOKEN_KEYWORD_ELSE)) { // 如果有 else 关键字
        match(TOKEN_KEYWORD_ELSE);
        else_statement = parse_statement(); // 解析 else 分支语句
    }
    return create_ast_if_statement(condition, then_statement, else_statement);
}

// 解析 while 语句
// while_statement ::= 'while' '(' expression ')' statement
static ASTNode *parse_while_statement() {
    match(TOKEN_KEYWORD_WHILE); // 匹配 while 关键字
    match(TOKEN_LPAREN);        // 匹配 '('
    ASTNode *condition = parse_expression(); // 解析循环条件
    match(TOKEN_RPAREN);        // 匹配 ')'
    ASTNode *body = parse_statement(); // 解析循环体语句
    return create_ast_while_statement(condition, body);
}

// 解析语句
// statement ::= declaration_statement | expression_statement | return_statement | if_statement | while_statement | compound_statement
static ASTNode *parse_statement() {
    if (check_token(TOKEN_KEYWORD_INT) || check_token(TOKEN_KEYWORD_VOID)) { // 检查是否是类型关键字,判断为声明语句
        return parse_declaration_statement();
    } else if (check_token(TOKEN_KEYWORD_RETURN)) {
        return parse_return_statement();
    } else if (check_token(TOKEN_KEYWORD_IF)) {
        return parse_if_statement();
    } else if (check_token(TOKEN_KEYWORD_WHILE)) {
        return parse_while_statement();
    } else if (check_token(TOKEN_LBRACE)) { // 如果是 '{',说明是复合语句
        return parse_compound_statement();
    } else {
        // 否则,尝试解析为表达式语句(包括空语句)
        return parse_expression_statement();
    }
}

// 解析复合语句 (代码块)
// compound_statement ::= '{' statement* '}'
static ASTNode *parse_compound_statement() {
    match(TOKEN_LBRACE); // 匹配 '{'
    ASTNode *compound_stmt_node = create_ast_compound_statement();

    // 循环解析语句,直到遇到 '}' 或文件结束
    while (!check_token(TOKEN_RBRACE) && !check_token(TOKEN_EOF)) {
        ASTNode *stmt = parse_statement();
        if (stmt) {
            ast_compound_statement_add_statement(compound_stmt_node->data.compound_stmt, stmt);
        } else {
            // 如果解析语句失败,可能是语法错误,或者遇到了无法识别的Token
            // 这里可以添加更复杂的错误恢复机制,但目前简单地退出
            parser_error("Failed to parse statement within compound statement.");
        }
    }
    match(TOKEN_RBRACE); // 匹配 '}'
    return compound_stmt_node;
}

// 解析函数声明
// function_declaration ::= type_specifier identifier '(' ')' compound_statement
static ASTNode *parse_function_declaration() {
    // 解析返回类型
    ASTNode *return_type_node = parse_type_specifier();
    char *return_type_name = strdup(return_type_node->data.identifier->name); // 复制类型名
    free_ast_node(return_type_node); // 释放临时类型节点

    // 解析函数名
    if (!check_token(TOKEN_IDENTIFIER)) {
        parser_error("Expected function name after return type.");
    }
    char *func_name = strdup(parser_current_token_g->value);
    match(TOKEN_IDENTIFIER);

    match(TOKEN_LPAREN); // 匹配 '('
    // TODO: 这里可以添加参数解析逻辑
    match(TOKEN_RPAREN); // 匹配 ')'

    ASTNode *body = parse_compound_statement(); // 解析函数体(复合语句)

    return create_ast_function_declaration(return_type_name, func_name, body);
}

// -----------------------------------------------------------------------------
// 解析器接口函数实现
// -----------------------------------------------------------------------------

// 解析器初始化
void parser_init() {
    // 获取第一个Token,启动词法分析器
    get_next_token();
    fprintf(stdout, "Parser initialized. First token: %s ('%s').\n",
            token_type_to_string(parser_current_token_g->type),
            parser_current_token_g->value ? parser_current_token_g->value : "");
}

// 解析整个程序
ASTNode *parse_program() {
    ASTNode *program_node = create_ast_program();

    // 循环解析函数声明,直到文件结束
    while (!check_token(TOKEN_EOF)) {
        ASTNode *func_decl = parse_function_declaration();
        if (func_decl) {
            ast_program_add_function_declaration(program_node->data.program, func_decl);
        } else {
            parser_error("Failed to parse function declaration.");
        }
    }
    return program_node;
}

// 释放解析器资源
void parser_cleanup() {
    if (parser_current_token_g) {
        free_token(parser_current_token_g);
        parser_current_token_g = NULL;
    }
    fprintf(stdout, "Parser cleaned up.\n");
}

// -----------------------------------------------------------------------------
// AST打印辅助函数 (用于调试和可视化AST)
// -----------------------------------------------------------------------------

// 辅助函数:打印缩进
static void print_indent(int indent) {
    for (int i = 0; i < indent; ++i) {
        printf("  "); // 每个缩进级别使用两个空格
    }
}

// 辅助函数:将ASTNodeType转换为可读字符串
const char *ast_node_type_to_string(ASTNodeType type) {
    switch (type) {
        case AST_PROGRAM: return "PROGRAM";
        case AST_FUNCTION_DECLARATION: return "FUNCTION_DECLARATION";
        case AST_COMPOUND_STATEMENT: return "COMPOUND_STATEMENT";
        case AST_VARIABLE_DECLARATION: return "VARIABLE_DECLARATION";
        case AST_RETURN_STATEMENT: return "RETURN_STATEMENT";
        case AST_IF_STATEMENT: return "IF_STATEMENT";
        case AST_WHILE_STATEMENT: return "WHILE_STATEMENT";
        case AST_EXPRESSION_STATEMENT: return "EXPRESSION_STATEMENT";
        case AST_BINARY_EXPRESSION: return "BINARY_EXPRESSION";
        case AST_UNARY_EXPRESSION: return "UNARY_EXPRESSION";
        case AST_ASSIGNMENT_EXPRESSION: return "ASSIGNMENT_EXPRESSION";
        case AST_IDENTIFIER: return "IDENTIFIER";
        case AST_INT_LITERAL: return "INT_LITERAL";
        case AST_STRING_LITERAL: return "STRING_LITERAL";
        default: return "UNKNOWN_AST_NODE_TYPE";
    }
}

// 辅助函数:将OperatorType转换为可读字符串
const char *operator_type_to_string(OperatorType op) {
    switch (op) {
        case OP_NONE: return "NONE";
        case OP_ADD: return "+";
        case OP_SUB: return "-";
        case OP_MUL: return "*";
        case OP_DIV: return "/";
        case OP_MOD: return "%";
        case OP_EQ: return "==";
        case OP_NE: return "!=";
        case OP_LT: return "<";
        case OP_GT: return ">";
        case OP_LE: return "<=";
        case OP_GE: return ">=";
        case OP_AND: return "&&";
        case OP_OR: return "||";
        case OP_BIT_AND: return "&";
        case OP_BIT_OR: return "|";
        case OP_BIT_XOR: return "^";
        case OP_LSHIFT: return "<<";
        case OP_RSHIFT: return ">>";
        case OP_ASSIGN: return "=";
        case OP_ADD_ASSIGN: return "+=";
        case OP_SUB_ASSIGN: return "-=";
        case OP_MUL_ASSIGN: return "*=";
        case OP_DIV_ASSIGN: return "/=";
        case OP_MOD_ASSIGN: return "%=";
        case OP_LSHIFT_ASSIGN: return "<<=";
        case OP_RSHIFT_ASSIGN: return ">>=";
        case OP_BIT_AND_ASSIGN: return "&=";
        case OP_BIT_OR_ASSIGN: return "|=";
        case OP_BIT_XOR_ASSIGN: return "^=";
        case OP_NEGATE: return "-(unary)";
        case OP_NOT: return "!(logical)";
        case OP_BIT_NOT: return "~(bitwise)";
        case OP_INCREMENT: return "++(unary)";
        case OP_DECREMENT: return "--(unary)";
        case OP_ADDRESS_OF: return "&(address_of)";
        case OP_DEREFERENCE: return "*(dereference)";
        default: return "UNKNOWN_OPERATOR";
    }
}

// 递归打印AST
void print_ast(ASTNode *node, int indent) {
    if (!node) {
        return;
    }

    print_indent(indent);
    printf("Node Type: %s", ast_node_type_to_string(node->type));

    switch (node->type) {
        case AST_PROGRAM:
            printf("\n");
            for (int i = 0; i < node->data.program->num_function_declarations; ++i) {
                print_ast(node->data.program->function_declarations[i], indent + 1);
            }
            break;
        case AST_FUNCTION_DECLARATION:
            printf(" (Name: %s, Return Type: %s)\n",
                   node->data.func_decl->name,
                   node->data.func_decl->return_type);
            print_indent(indent + 1); printf("Body:\n");
            print_ast(node->data.func_decl->body, indent + 2);
            break;
        case AST_COMPOUND_STATEMENT:
            printf(" (Statements: %d)\n", node->data.compound_stmt->num_statements);
            for (int i = 0; i < node->data.compound_stmt->num_statements; ++i) {
                print_ast(node->data.compound_stmt->statements[i], indent + 1);
            }
            break;
        case AST_VARIABLE_DECLARATION:
            printf(" (Type: %s, Name: %s)\n",
                   node->data.var_decl->type,
                   node->data.var_decl->name);
            if (node->data.var_decl->initializer) {
                print_indent(indent + 1); printf("Initializer:\n");
                print_ast(node->data.var_decl->initializer, indent + 2);
            }
            break;
        case AST_RETURN_STATEMENT:
            printf("\n");
            if (node->data.return_stmt->expression) {
                print_indent(indent + 1); printf("Expression:\n");
                print_ast(node->data.return_stmt->expression, indent + 2);
            }
            break;
        case AST_IF_STATEMENT:
            printf("\n");
            print_indent(indent + 1); printf("Condition:\n");
            print_ast(node->data.if_stmt->condition, indent + 2);
            print_indent(indent + 1); printf("Then:\n");
            print_ast(node->data.if_stmt->then_statement, indent + 2);
            if (node->data.if_stmt->else_statement) {
                print_indent(indent + 1); printf("Else:\n");
                print_ast(node->data.if_stmt->else_statement, indent + 2);
            }
            break;
        case AST_WHILE_STATEMENT:
            printf("\n");
            print_indent(indent + 1); printf("Condition:\n");
            print_ast(node->data.while_stmt->condition, indent + 2);
            print_indent(indent + 1); printf("Body:\n");
            print_ast(node->data.while_stmt->body, indent + 2);
            break;
        case AST_EXPRESSION_STATEMENT:
            printf("\n");
            if (node->data.expr_stmt->expression) {
                print_indent(indent + 1); printf("Expression:\n");
                print_ast(node->data.expr_stmt->expression, indent + 2);
            }
            break;
        case AST_BINARY_EXPRESSION:
            printf(" (Op: %s)\n", operator_type_to_string(node->data.binary_expr->op));
            print_indent(indent + 1); printf("Left:\n");
            print_ast(node->data.binary_expr->left, indent + 2);
            print_indent(indent + 1); printf("Right:\n");
            print_ast(node->data.binary_expr->right, indent + 2);
            break;
        case AST_UNARY_EXPRESSION:
            printf(" (Op: %s)\n", operator_type_to_string(node->data.unary_expr->op));
            print_indent(indent + 1); printf("Operand:\n");
            print_ast(node->data.unary_expr->operand, indent + 2);
            break;
        case AST_ASSIGNMENT_EXPRESSION:
            printf(" (Op: %s)\n", operator_type_to_string(node->data.assign_expr->op));
            print_indent(indent + 1); printf("Left (Target):\n");
            print_ast(node->data.assign_expr->left, indent + 2);
            print_indent(indent + 1); printf("Right (Value):\n");
            print_ast(node->data.assign_expr->right, indent + 2);
            break;
        case AST_IDENTIFIER:
            printf(" (Name: %s)\n", node->data.identifier->name);
            break;
        case AST_INT_LITERAL:
            printf(" (Value: %d)\n", node->data.int_literal->value);
            break;
        case AST_STRING_LITERAL:
            printf(" (Value: \"%s\")\n", node->data.string_literal->value);
            break;
        default:
            printf(" (Unhandled Node Type)\n");
            break;
    }
}


main.c:更新以集成语法分析器

现在,咱们把词法分析器和语法分析器整合起来,并打印出生成的AST。

#include "lexer.h"
#include "parser.h" // 包含语法分析器头文件
#include "ast.h"    // 包含AST头文件

#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. 清理AST内存
    printf("\n--- Cleaning up AST memory ---\n");
    free_ast_node(program_ast);
    printf("--- AST memory cleaned up ---\n");

    // 6. 清理词法分析器资源
    lexer_cleanup();
    
    // 7. 清理解析器资源 (主要是释放最后一个Token)
    parser_cleanup();

    printf("\nCompilation phase (Lexical and Syntax Analysis) completed successfully.\n");
    return EXIT_SUCCESS;
}


示例C语言源代码文件:test.c (更新,以测试更多语法规则)

为了测试我们的语法分析器,咱们创建一个新的 test.c 文件,包含更多我们支持的语法结构:

// test.c
/* This is a multi-line comment.
 * It spans multiple lines.
 */
int sum(int a, int b) { // This is a single-line comment
    int result = a + b;
    if (result > 10) {
        result = result * 2;
    } else {
        result = result - 5;
    }
    while (result < 0) {
        result++;
    }
    return result;
}

void main() {
    int x = 10;
    int y = 20;
    int z;
    z = x + y * 2; // Test operator precedence
    if (z == 50 || x != 10) {
        z += 100;
    }
    x++; // Increment operator
    y--; // Decrement operator
    int final_val = sum(x, y);
    printf("Final value: %d\n", final_val);
    return; // Void function return
}

注意:

  • 我们当前的语法分析器还不支持函数参数,所以 int sum(int a, int b) 这样的函数声明会被简化处理,参数会被忽略。这是为了简化本阶段的实现,后续可以扩展。

  • printf 函数调用目前也被视为一个普通的表达式语句,其内部的参数解析和类型检查将在语义分析阶段处理。

编译和运行
  1. 保存文件:将上述代码块分别保存为 ast.h, ast.c, parser.h, parser.c, lexer.h, lexer.ctest.c。确保 lexer.hlexer.c 是上一部分提供的最新版本。

  2. 编译:在终端中使用GCC编译(确保你安装了GCC):

    gcc -o mycompiler main.c lexer.c parser.c ast.c -Wall -Wextra -std=c99
    
    
    • -o mycompiler: 指定输出的可执行文件名为 mycompiler

    • main.c lexer.c parser.c ast.c: 要编译的所有源文件。

    • -Wall -Wextra: 开启所有常见和额外的警告。

    • -std=c99: 使用C99标准编译。

  3. 运行

    ./mycompiler test.c
    
    

你将看到程序输出词法分析的Token流,然后是语法分析器构建的抽象语法树的结构化打印!这棵树就是你的C代码在编译器内部的“骨架”!

原理分析与逻辑剖析:语法分析器是如何“理解”C代码的?

咱们的语法分析器,虽然是简易版,但它已经能解析C语言的核心结构了。它的“理解”过程,完全是基于我们定义的EBNF语法规则,通过递归下降的方式一步步进行的。

1. Token流驱动

语法分析器不像词法分析器那样直接操作字符流,它操作的是词法分析器生成的Token流get_next_token() 函数是它的核心,每次调用都会从词法分析器那里获取下一个Token,并更新 parser_current_token_g

2. match() 函数:规则的“守门员”

match(expected_type) 函数是递归下降解析的基石。它扮演着“守门员”的角色:

  • 检查:它会检查当前Token的类型是否符合预期的 expected_type

  • 消耗:如果匹配成功,它会“消耗”掉当前Token(通过调用 get_next_token() 获取下一个Token),表示这个Token已经被成功解析。

  • 错误报告:如果类型不匹配,它会立即报告语法错误,并终止程序(在实际编译器中,这里会有更复杂的错误恢复机制,但为了简化,我们直接退出)。

3. 递归下降:函数即规则

解析器的每个 parse_xxx() 函数都严格对应着EBNF语法规则中的一个非终结符。

  • parse_program():程序的入口,它会循环调用 parse_function_declaration() 来解析程序中的所有函数。

    • program ::= function_declaration*

  • parse_function_declaration():解析一个函数声明。它会依次调用其他解析函数来匹配 type_specifieridentifier'('')'compound_statement

    • function_declaration ::= type_specifier identifier '(' ')' compound_statement

  • parse_compound_statement():解析一个代码块。它会匹配 {,然后循环调用 parse_statement() 来解析块内的所有语句,直到匹配 }

    • compound_statement ::= '{' statement* '}'

  • parse_statement():这是一个分发函数,它会根据当前Token的类型,判断应该调用哪个具体的语句解析函数(如 parse_declaration_statement()parse_return_statement()parse_if_statement() 等)。

    • statement ::= declaration_statement | expression_statement | return_statement | if_statement | while_statement | compound_statement

4. 表达式解析:优先级和结合性

表达式的解析是递归下降中最巧妙的部分,它通过函数的嵌套调用来自然地处理运算符的优先级结合性

  • 优先级:优先级越高的运算符,其对应的解析函数被调用的层级越深。例如,parse_multiplicative_expression() 会调用 parse_unary_expression(),而 parse_additive_expression() 会调用 parse_multiplicative_expression()。这意味着乘除模运算会在加减运算之前被解析。

    • additive_expression ::= multiplicative_expression (('+' | '-') additive_expression)?

    • multiplicative_expression ::= unary_expression (('*' | '/' | '%') multiplicative_expression)?

    • unary_expression ::= ... primary_expression

    • primary_expression ::= ... (最高优先级)

  • 结合性

    • 左结合性(如 a + b + c 等价于 (a + b) + c):通过 while 循环和左递归(当前函数调用比它优先级低或同级的函数)来实现。例如,在 parse_additive_expression() 中,node = create_ast_binary_expression(op_type, node, parse_multiplicative_expression()); 这一行,node 会不断地作为左操作数被更新,从而形成左结合的AST结构。

    • 右结合性(如赋值运算符 a = b = c 等价于 a = (b = c)):通过右递归来实现。例如,在 parse_assignment_expression() 中,right_expr = parse_assignment_expression(); 这一行,右侧的表达式会再次调用自身,从而形成右结合的AST结构。

5. AST的构建

每个 parse_xxx() 函数在成功解析其对应的语法结构后,都会调用 create_ast_xxx() 函数来创建一个新的AST节点,并将其子节点连接起来。最终,所有的节点会形成一棵完整的AST。

  • 例如,parse_binary_expression() 会创建 AST_BINARY_EXPRESSION 节点,并将其左右操作数(通过递归调用其他解析函数获得)作为子节点。

6. 错误处理与恢复 (简易版)

目前我们的错误处理非常简单粗暴:一旦遇到语法错误,就 exit(EXIT_FAILURE)。在实际的编译器中,错误恢复是一个复杂的话题,通常会尝试跳过错误部分,继续解析,以便发现更多的错误,或者提供更友好的错误提示。但对于手撸编译器来说,简单退出是可接受的起点。

语法分析器工作流程(思维导图概念)

(这里想象一个流程图,描述 parse_program() 和其子函数的调用关系:

parse_program() -> parse_function_declaration() (循环) -> parse_type_specifier() -> match(TOKEN_IDENTIFIER) (函数名) -> match(TOKEN_LPAREN) -> match(TOKEN_RPAREN) -> parse_compound_statement() -> match(TOKEN_LBRACE) -> parse_statement() (循环) -> (根据Token类型分发到) parse_declaration_statement() parse_expression_statement() parse_return_statement() parse_if_statement() parse_while_statement() parse_compound_statement() (递归) -> match(TOKEN_SEMICOLON) (对于语句) -> match(TOKEN_RBRACE) -> match(TOKEN_EOF)

表达式解析的层级结构(AST结构图概念): z = x + y * 2; AST_ASSIGNMENT_EXPRESSION (=) Left: AST_IDENTIFIER (z) Right: AST_BINARY_EXPRESSION (+) Left: AST_IDENTIFIER (x) Right: AST_BINARY_EXPRESSION (*) Left: AST_IDENTIFIER (y) Right: AST_INT_LITERAL (2) )

这个流程图和AST结构图,清晰地展示了语法分析器如何将扁平的Token流,通过递归下降的方式,构建成具有层次结构的AST,从而“理解”代码的逻辑关系。

总结与展望:你已经拥有了编译器的“大脑”!

恭喜你,老铁!你已经成功地给你的C语言编译器装上了“大脑”——语法分析器

你现在应该明白:

  • 语法分析器是编译器的“阅读理解”专家,它负责检查Token流是否符合C语言的语法规则。

  • 它的输出是抽象语法树(AST),这是代码的“骨架”,移除了语法细节,只保留了核心语义结构。

  • 我们通过递归下降解析,为每条语法规则编写一个函数,通过函数的相互调用和匹配Token来构建AST,并巧妙地处理了运算符的优先级和结合性。

从字符流到Token流,再到抽象语法树,你已经一步步深入了C语言编译器的核心。你现在不仅仅是会写C代码,更开始理解C代码在计算机内部是如何被“理解”和表示的。是不是感觉对C语言的掌控力又提升了一个档次?

下一篇文章,我们将进入编译器的第三阶段——语义分析!我们将在这棵“骨架”(AST)上填充“血肉”,检查代码的含义是否合法,比如变量有没有定义、类型是否匹配、函数调用是否正确等等。那才是真正让编译器“理解”C语言代码“意思”的关键!

敬请期待!如果你觉得这篇文章让你醍醐灌顶,请务必点赞、收藏、转发,让更多想彻底搞懂C语言的兄弟们看到!你的支持是我继续创作的最大动力!

咱们下期不见不散!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值