Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

编译原理-笔记

概述

阅读一下龙书🐲。能增加计算机基础知识。

第一章 引论

定义一门语言的规则,按照规则编写代码,将代码翻译成机器能运行的程序叫做编译器(compiler)。研究编译器的编写将涉及到程序设计语言计算机体系结构形式语言理论算法软件工程

介绍内容:

  • 语言翻译器的不同形式;
  • 在高层次上论述一个典型编译器的结构;
  • 程序设计语言和硬件体系结构的发展趋势;这些趋势将影响编译器的形式。
  • 介绍关于编译器设计和计算机科学理论的关系的一些事实,并给出编译技术在编译领域之外的应用。
  • 简单论述论述研究编译器时用到的重要程序设计语言概念。

语言处理器

编译器就是阅读某种语言(在阅读过程中能检查语法是否正确),并且翻译成等价的目标语言的程序。

---
title: Figure 1-1 编译器
---
flowchart TB
    A>"源代码"] --- B[编译器] --- C>"目标程序"]

目标程序就是机器语言程序,用户就可以直接调用。

---
title: Figure 1-2 运行目标程序
---
flowchart LR
    A>"输入"] --- B[目标程序] --- C>"输出"]

解释器(interpreter)也是语言处理器。不会翻译生成目标程序。解释器直接用用户提供的输入执行源代码中的指定的操作。

---
title: Figure 1-3 一个解释器
---
flowchart LR
	A>源代码]
	B>输入]
	C[解释器]
	D>输出]
	
	%% 连接关系
	A & B --> C
	C --> D
  • 在将用户输入加工成输出处理过程,编译器产生的目标程序比解释器快很多。
  • 解释器的错误诊断效果要比编译器更好。(解释器是逐行执行源代码)

java程序是结合了编译、解释两个过程。翻译成字节码(bytecode),java的即时(just in time)编译器也能将其翻译成机器语言。通过网络传递到目标机器通过虚拟机或者直接在机器上运行。

---
title: Figure 1-4 一个混合编译器
---
flowchart TB

    %% ---------- 上半部分:纵向 TB ----------
    subgraph PART1[翻译阶段]
        direction TB
        A>源代码]
        B[翻译器]
        C[中间程序]
        A --> B --> C
    end

    %% ---------- 下半部分:横向 LR ----------
    subgraph PART2[执行阶段]
        direction LR
        D[输入]
        E[虚拟机]
        F[输出]
        D --> E --> F
    end
	
    %% ---------- 上下连接 ----------
    C --> E

图1-5解释:
将多个源代码文件聚合一块需要通过预编译器处理,并且将宏,模板展开成最终源代码。

编译器加工源代码产生有利于调试输出、调试的汇编代码

汇编器(assembler)加工汇编代码将其变成可重定向机器代码。

大型程序都是会将不同模块编译成不同的部分来编译。最终生成dll,so和可执行程序。在启动程序里面将会指定自己代码依赖哪些库文件。链接器(linker)解决外部内存地址问题。加载器(loader)把所有的可执行目标文件发到内存中执行。

---
title: Figure 1-5 一个语言处理系统
---
flowchart TB
	linkStyle default corners 90
	
    A>源代码]
    B[预处理器]
    C[编译器]
    D[汇编器]
    E[链接器/加载器]
    F>库文件
重定向对象文件]     G>目标机器代码]     A --> B --经过预处理的程序--> C     C --目标汇编语言--> D --可重定向机器代码--> E --> G     F --> E

编译器和解释器之间的区别是什么?

编译器是将你写的高级语言代码(如 C/C++/Java/Go)一次性全部翻译成计算机可以直接运行的机器码(可执行文件)的程序。

核心区别:

  • 编译器 (Compiler): 像一个翻译官,先把整本书(整个程序)翻译完,然后机器才能开始阅读(运行)。它在运行前工作。

  • 解释器 (Interpreter): 像一个同声传译,机器每读一句,它就翻译一句,翻译完一句就执行一句。它在运行时工作。

记住这两点:

  1. 编译器: 一次性,运行前,速度快。
  2. 解释器: 逐行翻译,运行时,方便调试。

编译器相对于解释器的优点是什么?解释器相对于编译器的优点是什么?

🚀 编译器的优势(运行前)

  • 运行快: 一次性翻译,执行速度飞快。
  • 优化好: 有时间慢慢优化代码,性能高。
  • 部署方便: 编译后生成独立的文件,不需要依赖其他工具就能运行。

🐢 解释器的优势(运行时)

  • 调试快: 发现错误立马停止,容易找到问题在哪。
  • 跨平台: 只要有解释器,代码就能在任何系统上跑。
  • 开发灵活: 不需要漫长等待编译,改了就能马上运行。

在一个语言处理系统中,编译器产生汇编语言而不是机器语言的好处是什么?


编译器产生汇编语言(而不是机器语言)的好处

将汇编语言作为中间输出,主要有以下四个方面的好处:

1. 可读性与可调试性 (Readability & Debuggability)

  • 答案要点:汇编语言是人类可读的助记符,而机器语言是二进制数值。

  • 解释:编译器开发者可以直接阅读生成的汇编代码,检查逻辑是否正确,极大地降低了编译器本身的开发和调试难度。

2. 简化编译器的设计 (Simplifies Compiler Design)

  • 答案要点:通过输出汇编,编译器可以将复杂的底层任务“外包”给汇编器。

  • 解释:生成机器码需要处理复杂的二进制指令格式(如操作码填充、位偏移等),而生成汇编只需输出文本。这样编译器可以专注于高级逻辑(如优化和寄存器分配)。

3. 符号化处理与地址延迟绑定 (Symbolic Labels)

  • 答案要点:汇编语言允许使用符号标签(Labels)代替具体的内存地址。

  • 解释:在汇编阶段,编译器不需要计算跳转指令的具体偏移量或变量的物理地址。这些繁琐的地址计算和重定位工作交由**汇编器(Assembler)链接器(Linker)**自动完成。

4. 利用现有工具链的优化 (Leveraging Toolchains)

  • 答案要点:汇编器通常自带针对特定硬件的微调和指令优化。

  • 解释:不同的操作系统对目标文件(如 ELF, PE 格式)有不同的封装要求。输出汇编语言使得编译器能够利用系统自带的专业工具链,确生成的目标程序符合该系统的标准。


💡 背诵口诀(高分必备关键词):

“易读、易调、不计地址、交给后道。”

  • 易读/易调:方便人看,方便找错。

  • 不计地址:不用自己算二进制跳转偏移,用标签(Labels)就行。

  • 交给后道:把生成的重任交给专业的汇编器和链接器。


把一种高级语言翻译成另一种高级语言的编译器成为源到源(source-to-source)的翻译器。编译器使用c语言作为目标语言有什么好处?

将 C 语言作为中间或目标语言(通常称为 C 这种“高级汇编”)有以下核心优势:

  1. 极佳的可移植性 (Portability):

    C 语言的编译器(如 GCC, Clang)几乎存在于所有硬件平台和操作系统上。通过输出 C 代码,源语言可以立即运行在任何支持 C 的设备上,无需为每个架构重写后端。

  2. 借用成熟的优化器 (Leveraging Optimizers):

    现代 C 编译器(如 GCC/LLVM)拥有数十年的优化积累。源到源翻译器只需生成逻辑正确的 C 代码,就可以利用这些工具进行复杂的指令调度、循环优化和寄存器分配。

  3. 开发效率高:

    相比于直接生成二进制机器码或汇编,生成 C 代码难度更低,调试也更容易。开发者可以像阅读源码一样阅读编译器生成的中间 C 代码。

  4. 生态系统集成:

    C 语言可以轻松调用现有的 C 库和系统 API,这使得源语言在处理底层系统调用时更加方便。

描述一下编译器所要完成的一些任务。

你好!很高兴能为你的编译原理作业提供支持。为了帮你拿到高分,我将针对这两个问题给出专业、简洁且符合学术规范的答案。

编译器的核心任务是将源代码转换为目标代码,这个过程通常分为 分析 (Analysis)综合 (Synthesis) 两个部分,具体包含以下任务:

1. 词法分析 (Lexical Analysis / Scanning)

  • 任务:读入源程序字符流,根据构词规则将其分割成一系列有意义的单元——记号 (Tokens)(如关键字、标识符、运算符)。

2. 语法分析 (Syntax Analysis / Parsing)

  • 任务:将 Tokens 组织成层次化的结构——语法树 (Syntax Tree),用于检查代码是否符合程序设计语言的语法规则。

3. 语义分析 (Semantic Analysis)

  • 任务:检查源程序是否符合语义逻辑(例如:变量是否先声明后使用、函数参数个数是否匹配、类型检查等)。

4. 中间代码生成 (Intermediate Code Generation)

  • 任务:将语法树转换为一种逻辑简单、与具体机器无关的表达形式(如三地址码或 P-code),便于后续优化。

5. 代码优化 (Code Optimization)

  • 任务:对中间代码进行等价变换,使最终生成的程序更(减少执行时间)或更(减少空间占用)。

6. 目标代码生成 (Code Generation)

  • 任务:将优化后的中间代码翻译成特定机器的汇编语言机器指令,并分配寄存器和内存。

7. 符号表管理与错误处理 (Symbol Table & Error Handling)

  • 任务:在全过程中记录变量名及其属性,并在发现语法或语义错误时向用户报告准确的位置和原因。

第二章 一个简单的语法制导翻译器

2.1 引言

一个语言的语法(syntax)描述了该语言的程序都正确形式;而该语言的语义(seantics)则定了程序的含义,即每个程序在运行时做什么事情。将会在 2.2 语法定义 给出广泛使用的表示方法来描述语法(synta),这个方法就是上下文无关文法或者BNF(Backus-Naur范式)。用现有的语义表示方法来描述一个语言的语义的难度大于描述语言的语法的难度。

💡人去描述语义非常困难,而且用BNF去描述也是非常困难。所以这里提出了新的工具来描述。

flowchart TB

    %% 节点
    A[源代码
Source Code] B[词法分析
Lexer] C[语法分析
Parser / AST] D[符号表
Symbol Table] E[代码生成器
Code Generator] F[目标代码
Output Code] %% 主流程 A --> B --> C --> E --> F %% 符号表的关联 C -->|定义/查询符号| D E -->|读取符号信息| D

2.2 语法定义

graph TD
    A[expr] --> B1[expr1]
    A --> B2(➕)
    A --> B3[term]
    A --> B4["{print(➕)}"]
	B1 --> C1[expr2]

Figure 2-14 把9-5+2翻译成95-2+的语义动作