深入解析cool编译器:内部机制与应用

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:编译器是计算机科学中的核心工具,负责将高级语言转换为机器代码。本文将通过解构”cool”编译器的”work_coolc.tar.gz”压缩包,探讨其工作原理和设计细节。cool编译器涉及多个编译阶段,包括词法分析、语法分析、语义分析和代码生成。文章还讨论了cool编译器在命令行接口、错误处理、优化策略、兼容性以及文档和社区支持等方面的特点,为用户提供了全面理解并有效应用cool编译器的能力。
work_coolc.tar.gz

1. 编译器定义与作用

1.1 编译器简介

编译器是一种特殊的软件工具,它能够将高级编程语言编写的源代码转换为机器能理解的目标代码(通常是机器码或字节码)。其主要功能是进行代码翻译,使得不同的计算机硬件或平台能够执行相同的程序。

1.2 编译器的作用

编译器在软件开发中扮演了核心角色。它不仅负责代码转换,还涉及了代码优化、错误检查、警告提示等多方面工作。通过编译器,开发者能够专注于编写高级抽象的代码,而不必深入了解底层硬件细节。

1.3 编译器的分类

根据不同的编译策略,编译器可以分为两类:静态编译器和动态编译器。静态编译器在程序运行前完成所有编译工作,而动态编译器则在程序运行时边执行边编译。此外,还有解释器这种中间形式,它边解释边执行代码,不产生目标代码。

2. “cool”编译器工作流程

“cool”编译器是针对C语言源代码进行编译的一个软件工具,它通过将源代码转换为机器码,使得程序可以在计算机硬件上运行。让我们深入探讨”cool”编译器的工作流程,包括它的主要阶段和架构。

2.1 编译器的主要阶段

编译器的主要阶段分为三个部分:预处理阶段、编译阶段和链接阶段。每个阶段都有它独特的功能和目的,确保源代码能被正确转化为可执行文件。

2.1.1 预处理阶段

在预处理阶段,编译器处理源代码中所有预处理指令,比如宏定义(#define)、文件包含(#include)以及条件编译指令(#ifdef、#ifndef等)。预处理器会在编译之前修改源代码,为编译器准备最终的源文件。

// 示例:预处理指令示例
#define PI 3.14159
#include <stdio.h>

int main() {
    printf("Value of PI : %f\n", PI);
    return 0;
}

上述代码中的 #define PI 3.14159 是一个宏定义,预处理程序将所有的PI替换为3.14159。 #include <stdio.h> 指令告诉预处理器将标准输入输出头文件的内容插入到当前源文件中。

预处理器的输出是一个预处理后的C文件,它去除了所有的预处理指令,只包含了实际要编译的代码。

2.1.2 编译阶段

编译阶段是编译器的核心部分,它将预处理后的源代码翻译成机器语言。编译过程通常可以细分为以下几个子阶段:

  • 词法分析(Lexical Analysis)
  • 语法分析(Syntax Analysis)
  • 语义分析(Semantic Analysis)
  • 中间代码生成(Intermediate Code Generation)
  • 代码优化(Code Optimization)
  • 目标代码生成(Target Code Generation)
// 词法分析的简单例子
int number = 123;

词法分析器会将上述代码分解为词法单元,例如 int number = 123 等。这些单元会进一步被用来构建抽象语法树。

2.1.3 链接阶段

链接阶段是在编译阶段完成后的一个重要步骤,它把编译器生成的一个或多个目标文件(.o或.obj文件)与标准库链接起来,形成一个最终的可执行文件(.exe)。链接器处理的典型任务包括:

  • 符号解析:将程序中用到的所有变量和函数的实际地址分配给引用它们的地方。
  • 地址重定位:根据程序的内存布局修正程序中所有对地址的引用。
  • 外部库的链接:如果程序使用了外部库,链接器会将这些库中的相应部分引入最终的可执行文件中。

链接过程的复杂性通常与程序中引用的外部符号数量成正比。

2.2 “cool”编译器的架构

“cool”编译器的设计对于其性能和扩展性至关重要。架构的设计需要考虑到各个阶段间的耦合程度,以及如何高效地将用户代码转化为机器码。

2.2.1 模块化设计

“cool”编译器采用模块化设计,每个阶段都由一个或多个模块组成。这种设计允许开发者独立地修改或优化编译器的某个特定部分,而不影响其他部分。模块化的另一个优点是可以更容易地支持多种编程语言和目标平台。

2.2.2 流程控制机制

“cool”编译器的流程控制机制管理着各个编译阶段的执行流程。它确保了编译任务以正确顺序执行,以及能够有效地处理阶段间的依赖关系。对于开发者来说,了解流程控制机制是深入理解和优化编译器性能的关键。

2.2.3 编译器与编程语言的对应关系

“cool”编译器的架构需要紧密贴合其编译目标语言的特性。例如,它需要理解C语言的语法、语义以及标准库的调用约定。这关系到如何设计词法分析器、语法分析器和代码生成器以高效准确地编译C代码。

在这个阶段,编译器架构师需要特别注意语言的特性,如指针操作、内存管理、并行处理等,因为这些特性对编译器的设计和优化有直接的影响。

graph LR
A[源代码] --> B[预处理阶段]
B --> C[编译阶段]
C --> D[链接阶段]
D --> E[可执行文件]

以上流程图概括了编译器从源代码到最终可执行文件的完整流程。

通过以上细致的分析,我们能够理解”cool”编译器是如何将C源代码转换为机器码的。在接下来的章节中,我们将继续探讨”cool”编译器在词法分析和语法分析方面的工作机制。

3. 词法分析与标记生成

在编译器的前端处理过程中,词法分析是第一个实际分析源代码的阶段。它负责将字符序列转换为一系列的词法单元(tokens),而这些词法单元正是编程语言中的基本元素,如标识符、关键字、运算符和分隔符等。词法分析器通常会在读取源代码文件时,同时创建对应的标记,以便后续阶段的处理。

3.1 词法分析的原理

3.1.1 词法单元与正则表达式

词法单元,也称为标记(token),是编译器能理解的最小语言单位。它们通常由正则表达式定义,这些表达式描述了标记的模式。例如,在C语言中,一个标识符可能由字母或下划线开头,后跟零个或多个字母、数字或下划线。

一个正则表达式的例子是:

[a-zA-Z_][a-zA-Z0-9_]* 

此表达式匹配以字母或下划线开头,后跟任意数量的字母、数字或下划线的标识符。

3.1.2 状态机与词法分析器实现

词法分析器通常是通过确定有限状态自动机(DFA)或非确定有限状态自动机(NFA)实现的。DFA在任何时候都处于一个状态,并且对于任何输入字符,DFA都会转移到另一个状态或者保持当前状态。

例如,考虑一个简单的状态机,用于识别C语言中的关键字 int

state: START
input: 'i' 
next state: INT1

state: INT1
input: 'n' 
next state: INT2

state: INT2
input: 't' 
next state: MATCH

state: MATCH
input: <any other> 
next state: ERROR

在实现词法分析器时,源代码字符流会被逐个读取,并根据状态机的规则进行处理。每当到达一个匹配的结束点,就会输出一个标记。

3.2 标记的生成与处理

3.2.1 标记的作用与格式

标记是编译器后续阶段处理的中间表示形式。它们不仅携带词法单元的类型信息(如:关键字、运算符、标识符等),还可能包含词法单元的字面值或引用(如标识符或数字常量的文本)。

一个典型的标记格式可能如下所示:

Token: TYPE, LITERAL

例如,对于源代码中的 int a = 5; 这段内容,标记生成可能如下:

Token: KEYWORD, int
Token: IDENTIFIER, a
Token: ASSIGNMENT_OPERATOR, =
Token: INTEGER_LITERAL, 5
Token: SEMICOLON, ;

3.2.2 错误检测与报告机制

在标记生成的过程中,词法分析器需要能够检测出源代码中的非法字符或结构,并向用户报告错误。这通常通过维护一个错误计数器和记录错误位置的机制来完成。当一个错误被检测到时,词法分析器会生成一个特殊的错误标记,并输出一个错误消息,指明错误的性质和位置。

例如:

Error: Unexpected character 'x'

并指向出错的代码位置,这样编译器和用户都能明白源代码中存在什么问题。

在本章节中,我们深入探讨了词法分析器的核心概念、实现机制以及如何处理生成标记。我们通过正则表达式定义词法单元,展示了状态机在词法分析中的作用,并且讨论了标记的格式及其在错误检测和报告中的应用。通过本章节的介绍,读者应该能够更好地理解编译器前端处理中词法分析的重要性和工作原理。

在下一章节中,我们将深入语义分析与类型检查领域,继续探讨编译器如何进一步理解和验证编程语言的结构和意义。

4. 语法分析与抽象语法树(AST)构建

4.1 语法分析的策略

4.1.1 上下文无关文法

上下文无关文法(Context-Free Grammar,CFG)是形式语言理论中描述编程语言语法的一种工具,它在语法分析中起到了核心作用。CFG由一组产生式规则(production rules)组成,每条规则描述了一个非终结符如何被其它的非终结符或终结符替代。在编译过程中,语法分析器使用CFG来识别源代码的结构,并将其转化为内部表示形式,如抽象语法树(AST)。

产生式通常具有以下形式: A → α ,其中 A 是一个非终结符,而 α 是一个终结符或非终结符的序列。例如,在一个简单的算术表达式中, E → E + T 表示表达式(E)可以由一个表达式后跟一个加号和一个项(T)构成。

4.1.2 递归下降分析法

递归下降分析是实现语法分析的一种简单直观的方法,它基于CFG的规则直接进行递归函数的实现。每个非终结符通常对应一个递归函数,解析输入字符串时,根据当前的非终结符和输入符号调用相应的函数。

一个简单的递归下降函数可能看起来像这样:

def parse_expression():
    parse_term()
    if next_token() == '+':
        match('+')
        parse_expression()

def parse_term():
    parse_factor()
    if next_token() in ['-', '*']:
        operator = next_token()
        parse_term()

def parse_factor():
    if next_token() in ['number', 'identifier']:
        match(next_token())
    elif next_token() == '(':
        match('(')
        parse_expression()
        match(')')

这个递归下降分析器会尝试解析一个算术表达式,如 3 + 4 * 2 。它从表达式开始,递归地解析项(term)和因子(factor)直到匹配到全部输入。

4.1.3 语法分析的挑战

尽管递归下降分析直观且易于实现,但直接使用它来解析复杂语言的语法会面临挑战。对于包含大量左递归的文法规则,或者需要回溯的语境,递归下降分析可能不够高效或直接实现困难。现代编译器往往采用LL、LR、或者更高级的解析器生成器,如Yacc/Bison来处理更复杂的文法和减少开发者的编码负担。

4.2 抽象语法树(AST)的构建

4.2.1 AST节点的定义

抽象语法树(AST)是源代码语法结构的高度抽象表示形式。每个节点代表一个语言构造,例如一个表达式、语句或声明。在语法分析阶段,编译器会根据解析出的语法结构创建AST。

AST节点通常包括如下内容:

  • 节点类型:表示节点代表的构造类别,如表达式、声明等。
  • 子节点:构成该构造的具体细节。
  • 元数据:位置信息、作用域等附加信息。

例如,一个简单的表达式 a = b + c 可以被解析为如下的AST结构:

graph TD;
    assign[=] --> left[a]
    assign --> right[+]
    right --> right_left[b]
    right --> right_right[c]

在实际的编译器实现中,AST的每个节点是一个对象或结构体,并且会拥有指向下级节点的链接。

4.2.2 从标记到AST的转换过程

将标记序列转换为AST的过程是语法分析的核心,涉及到迭代地读取标记并根据语法规则构建出树状的数据结构。这个过程可以分为几个步骤:

  1. 初始化:创建一个根节点。
  2. 规则应用:根据当前读取的标记和文法规则,决定下一步如何构建AST节点。
  3. 子节点创建:对于复合结构,递归地创建子节点。
  4. 树构建完成:当所有标记都被处理,AST就构建完成。

下面是一个简化的Python代码示例,它展示了如何从标记列表构建一个简单的AST:

class ASTNode:
    def __init__(self, type, value):
        self.type = type
        self.value = value
        self.children = []

def build_ast(tokens):
    # 这里只是一个伪代码示例
    # 实际过程会更复杂,需要考虑所有的语法规则
    root = ASTNode('root', None)
    current = root
    for token in tokens:
        # 基于当前token类型来决定如何构建树
        if token.type == 'NUMBER':
            child = ASTNode('NUMBER', token.value)
            current.children.append(child)
        elif token.type == 'OPERATOR':
            child = ASTNode('OPERATOR', token.value)
            current.children.append(child)
            current = child  # 移动到操作符节点,因为下一个数字应该是操作数

    return root

在实际的编译器中,构建AST的过程需要处理复杂的语法规则,并可能涉及到错误恢复机制,以便在语法错误发生时,编译器仍可以继续解析后续代码。

AST的构建是编译过程中关键的一步,因为它为后续的语义分析、优化和代码生成提供了基础结构。准确和高效的AST构建是高质量编译器的一个重要特征。

5. 语义分析与类型检查

语义分析和类型检查是编译过程中的关键步骤,它们确保程序中的表达式、语句和代码块不仅符合语法规则,而且具有正确的意义,即语义。在编译器的高级阶段,语义分析器会根据语言的语义规则来检查程序的正确性,类型检查器会确保变量、表达式和函数调用中的类型使用是合法的。

5.1 语义分析的重要性

语义分析确保程序的结构和行为符合语言定义的规则,包括变量的作用域、可见性和生命周期,控制流结构的正确性以及表达式的逻辑一致性。

5.1.1 静态类型检查

静态类型检查是指在编译时期对类型信息进行检查的过程。这种方式可以捕捉到如类型不匹配、无效类型转换等运行时错误,从而提高程序的安全性和稳定性。例如,C语言的编译器会检查所有运算符的左右操作数类型是否兼容。

int result = "string" + 10; // 编译错误,类型不匹配

5.1.2 符号表的构建与使用

符号表是一个记录了程序中所有符号(如变量、函数名等)以及它们相关信息(类型、作用域等)的数据结构。在语义分析阶段,符号表被用来存储和查找符号信息,确保每个符号都被正确地引用和声明。

5.2 类型检查的实现

5.2.1 类型推导算法

类型推导算法试图在不显式声明变量类型的情况下,根据变量的使用方式和上下文环境来推断其类型。例如,Haskell是一种支持类型推导的函数式编程语言。

add x y = x + y -- 编译器推断 x 和 y 都是 Num 类型

5.2.2 错误检测与类型不匹配处理

类型不匹配错误发生在程序尝试对不兼容类型的值进行操作时。好的类型检查器不仅需要发现错误,还要提供明确的错误信息和尽可能准确的错误定位,以帮助程序员快速定位和修正问题。

result = 10 + "a" # 错误,整数和字符串不能相加

5.2.3 类型转换与一致性

在类型检查的过程中,编译器也会处理类型转换问题,其中涉及到隐式类型转换和显式类型转换(强制类型转换)。隐式转换必须在严格定义的规则下进行,而显式转换则需要程序员明确指定转换的类型。

let num = 42; // Rust中num的类型推断为i32
let float_num: f64 = num as f64; // 显式类型转换

通过分析代码中的类型使用情况,编译器能够捕捉到潜在的类型安全问题,这些检查是程序正确运行的保障。语义分析器和类型检查器通常需要对编程语言有深入的理解,以正确实现这些功能。

在接下来的章节中,我们将继续探讨编译器的后端部分,即代码生成和目标代码转换,这是将高级语言的抽象转化为机器语言的关键步骤。

6. 代码生成与目标代码转换

6.1 代码生成的基本原理

6.1.1 中间代码表示

代码生成是编译过程中的一个关键步骤,它涉及将抽象语法树(AST)转换为某种形式的中间代码表示(Intermediate Code Representation, ICR)。中间代码是编译器设计中的一个重要概念,因为它为源代码提供了一个与机器无关的抽象表示,使得代码可以更容易地被优化,并且支持多种目标平台。

中间代码通常具备以下特性:

  • 独立于平台 :它不直接依赖于任何特定的硬件或软件平台,这使得相同的中间代码可以被用于不同的目标系统。
  • 独立于语言 :它可以被设计为适用于多种编程语言,简化了多语言编译器的前端工作。
  • 便于优化 :中间代码可以被设计为便于进行各种变换和优化操作。
  • 适于代码生成 :最终将其转换为目标机器代码的过程变得更加直接。

常见的中间代码表示形式包括三地址代码(Three-Address Code)、静态单赋值形式(Static Single Assignment, SSA)、栈机代码等。在这些形式中,三地址代码较为常见,它包含了限制性的指令集,每条指令最多涉及三个操作数。

// 一个简单的例子:将x和y相加,并将结果存入z。
z = x + y;

在三地址代码形式下,上述语句可能会被表示为:

t1 = x + y
z = t1

其中 t1 是一个临时变量,用于存放中间结果。三地址代码的形式使得它很容易被转换成目标代码。

6.1.2 代码优化技术

代码生成不仅仅是将AST翻译成中间代码,还包括了对生成的代码进行优化的过程。代码优化是提高程序性能和降低资源消耗的重要手段。优化可以在多个层面进行,包括局部优化、循环优化、全局数据流分析和寄存器分配等。

局部优化侧重于优化单个基本块内的指令序列,例如删除无用的赋值指令或合并相同的计算结果。循环优化则关注于循环结构,可能包括减少循环内部的计算次数或提前退出循环。

全局数据流分析通过分析整个程序的执行流程,挖掘变量的使用情况、发现可能的死码,并对程序的控制流图(CFG)进行分析,以识别潜在的优化机会。

寄存器分配是编译器优化的另一个关键领域,它涉及到将程序中频繁使用的变量映射到处理器的有限寄存器中。高效的寄存器分配可以减少内存访问次数,提升程序运行速度。

代码优化过程的一个简单例子是:

a = b + c;
d = a + e;

通过窥孔优化(Peephole Optimization),编译器可以发现并消除冗余的计算,因为两次使用了变量 a 的值,可以优化为:

t1 = b + c;
d = t1 + e;

这里 t1 是临时变量,用于保存 b + c 的结果,从而避免重复的加法计算。

6.2 目标代码转换

6.2.1 汇编语言生成

将中间代码转换为目标机器的汇编语言是代码生成的下一站。这个过程通常由后端编译器负责,它会根据目标架构的特性将抽象的中间指令映射到具体的机器指令。

这个过程需要对目标机器的指令集架构(Instruction Set Architecture, ISA)有深刻的理解。ISA定义了处理器支持的操作类型、数据类型、寄存器、寻址模式和控制流指令等。编译器后端需要生成符合ISA的汇编指令,并进行适当的寄存器分配和指令调度。

例如,如果目标机器是x86架构,则编译器会将中间代码转换为x86汇编指令。考虑下面的中间代码:

t1 = a + b
t2 = c + d
result = t1 + t2

转换为x86汇编可能类似于:

mov eax, [a]
add eax, [b]
mov ebx, [c]
add ebx, [d]
add eax, ebx
mov [result], eax

在这里,寄存器 eax ebx 被用来存储中间结果。

6.2.2 机器码生成与链接

汇编语言是人类可读的,但现代计算机无法直接执行。因此,汇编语言必须通过汇编器转换为机器码,这是处理器可以直接执行的二进制指令。

在生成机器码之后,链接器会将程序中所有的代码和数据段合并,解决所有的外部符号引用,并将程序的所有部分组合成最终的可执行文件。链接器还会进行符号解析、地址重定位等操作。

链接过程可能涉及到:

  • 静态链接 :将所有需要的库文件直接包含在最终的可执行文件中。
  • 动态链接 :将引用的库文件保留在外部,仅在程序执行时动态加载。

链接过程的复杂度可能会很高,因为程序通常包含对库函数和其他模块的引用。链接器负责解析这些引用,并创建一个运行时环境,允许程序执行。

例如,一个简单的链接过程可能涉及到将若干编译单元和库文件链接到一个单一的可执行文件:

gcc -o final_program file1.o file2.o -lmath

这里 file1.o file2.o 是编译单元, -lmath 指定链接数学库 libm.so

为了展示链接过程中处理的复杂性,下面是链接器处理的几个主要任务的概览:

  1. 符号解析:解决所有外部符号引用,如函数和变量的定义。
  2. 地址分配:为每个符号分配一个运行时地址。
  3. 重定位:如果指令或数据没有被分配到期望的地址,链接器需要修改它们以反映实际地址。
  4. 动态链接处理:设置动态加载和重定位的环境。

在链接完成后,我们最终得到了一个可由操作系统加载并执行的程序。

以上内容不仅展示编译器代码生成阶段到目标代码转换的过程,也涵盖了这一过程中编译器所执行的关键操作和背后的原理。从中间代码表示到优化技术,再到汇编语言生成与机器码的最终链接,都是编译器设计中不可或缺的环节。这些过程不仅需要对硬件架构和指令集有深入理解,还需要高效的算法和数据结构来支持。每个步骤都是精心设计的,目的是为了提升最终程序的性能,同时保持代码的可读性和可维护性。

7. “coolc-compiler”文件夹内容分析

在本章节中,我们将深入探究”coolc-compiler”文件夹内的内容,分析其结构、主要文件的功能,以及源代码文件的具体细节。通过本章节的学习,你将能够更好地理解和使用coolc编译器。

7.1 “coolc-compiler”结构解析

7.1.1 文件与目录布局

在”coolc-compiler”文件夹的根目录下,你可以发现一系列文件和子目录,它们按照功能进行了合理的分类。一般来说,常见的布局包括以下几个部分:

  • src :存放编译器的源代码。
  • include :存放编译器头文件,提供接口定义。
  • lib :存放编译过程中需要的库文件。
  • bin :存放编译器生成的可执行文件。
  • doc :存放编译器的文档说明。
  • Makefile :构建系统的主配置文件。

7.1.2 主要文件功能概述

以下是一些关键文件及其功能的概述:

  • src/main.c :编译器的入口文件,负责程序的初始化和调用编译流程。
  • src/parser.c :实现语法分析的主要文件。
  • src/ast.h :定义抽象语法树(AST)结构的头文件。
  • include/utils.h :包含工具函数和宏定义,用于提高代码复用性。
  • Makefile :编译指令文件,用于定义编译和链接过程。

7.2 文件夹内容的深入探究

7.2.1 源代码文件分析

src 目录下,源代码文件是编译器功能实现的核心。以 main.c 为例,以下是一段关键的代码片段,展示了程序初始化和调用编译流程的过程:

#include <stdio.h>
#include "parser.h"
#include "ast.h"

int main(int argc, char **argv) {
    // 初始化编译器环境
    init_compiler(argc, argv);

    // 编译源代码文件
    if (compile_file(input_file)) {
        // 如果编译成功,可能还会进行代码优化和生成目标文件等后续步骤
        generate_target_code();
    } else {
        // 如果编译失败,打印错误信息
        fprintf(stderr, "Compilation failed.\n");
    }

    // 清理编译器环境
    cleanup_compiler();

    return 0;
}

void init_compiler(int argc, char **argv) {
    // 初始化代码...
}

bool compile_file(const char *filename) {
    // 实现编译一个文件的具体逻辑...
    return true;
}

void generate_target_code() {
    // 生成目标代码逻辑...
}

7.2.2 构建系统与Makefile解析

构建系统是编译器开发中的重要组成部分,它负责协调源代码文件、依赖关系和生成可执行文件的整个过程。在coolc编译器中, Makefile 文件定义了构建规则:

CC=gcc
CFLAGS=-Wall -g
TARGET=coolc

all: $(TARGET)

$(TARGET): src/main.o src/parser.o src/ast.o
    $(CC) -o $@ $^ $(CFLAGS)

src/%.o: src/%.c
    $(CC) -c $< -o $@ $(CFLAGS)

clean:
    rm -f $(TARGET) *.o

在这个 Makefile 中,你可以看到:

  • 编译器的主目标文件是 coolc
  • 源代码文件被编译成 .o 对象文件。
  • 使用 gcc 作为编译器, -Wall -g 作为编译标志。
  • all 规则确保所有必要的目标都被构建。
  • clean 规则用于清理构建目录,移除所有生成的文件。

通过以上的分析,我们对”coolc-compiler”有了更深层次的了解。下一章节将继续深入,探讨在使用coolc编译器时,如何进行有效的代码优化和性能调优。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:编译器是计算机科学中的核心工具,负责将高级语言转换为机器代码。本文将通过解构”cool”编译器的”work_coolc.tar.gz”压缩包,探讨其工作原理和设计细节。cool编译器涉及多个编译阶段,包括词法分析、语法分析、语义分析和代码生成。文章还讨论了cool编译器在命令行接口、错误处理、优化策略、兼容性以及文档和社区支持等方面的特点,为用户提供了全面理解并有效应用cool编译器的能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值