1、编译器六大阶段
| 阶段 | 核心任务 | 输入 | 输出 | 常见错误 |
|---|---|---|---|---|
| 词法分析 | 拆分字符流为记号流 | 源程序(字符流) | 记号流 | 非法字符、字符串未闭合 |
| 语法分析 | 验证语法结构,生成语法树 | 记号流 | 语法分析树 | 括号不配对、缺少分号 |
| 语义分析 | 验证语义正确性,收集类型信息 | 语法分析树 | 带语义的 AST、符号表 | 变量未定义、类型不匹配 |
| 中间代码生成 | 生成与机器无关的中间代码 | 带语义的 AST | 三地址码等 | 无(前端错误已全部检查) |
| 代码优化 | 提高代码效率和减少大小 | 中间代码 | 优化后的中间代码 | 无 |
| 目标代码生成 | 生成特定机器的目标代码 | 优化后的中间代码 | 汇编代码或机器代码 | 无 |
2、后缀式
二、方法二:标准栈算法(通用所有情况,编译器实际使用)
这是最严谨、最通用的转换方法,也是所有编译器和计算器内部实现的算法。它可以处理任何合法的中缀表达式,包括含一元运算符、右结合运算符的情况。
核心数据结构
- 一个运算符栈:用于临时存储运算符和括号
- 一个输出队列:用于存储最终的后缀式
完整算法规则
- 初始化:创建空栈和空输出队列
- 从左到右逐个扫描中缀式的每个元素 :
- 遇到操作数:直接加入输出队列
- 遇到左括号
(:压入栈顶 - 遇到右括号
):- 弹出栈顶元素,加入输出队列
- 重复步骤 1,直到遇到左括号
- 弹出左括号,不加入输出队列
- 遇到运算符
op:- 当栈不为空,且栈顶元素不是左括号,且栈顶运算符的优先级 ≥ 当前运算符的优先级 时:
- 弹出栈顶运算符,加入输出队列
- 将当前运算符
op压入栈顶
- 当栈不为空,且栈顶元素不是左括号,且栈顶运算符的优先级 ≥ 当前运算符的优先级 时:
- 扫描结束:将栈中所有剩余元素依次弹出,加入输出队列
运算符优先级表(常用)
| 运算符 | 优先级 | 结合性 |
|---|---|---|
() |
最高 | 左结合 |
^(幂运算) |
4 | 右结合 |
*、/、% |
3 | 左结合 |
+、-(二元) |
2 | 左结合 |
-(一元负号) |
5 | 右结合 |
完整实例演示
转换表达式:a + b * (c - d) / e ^ f
表格
| 扫描元素 | 操作 | 栈内容(栈底→栈顶) | 输出队列 | 说明 |
|---|---|---|---|---|
a |
操作数,入队 | [] |
[a] |
|
+ |
栈空,压栈 | [+] |
[a] |
|
b |
操作数,入队 | [+] |
[a, b] |
|
* |
栈顶+优先级 (2) < *(3),压栈 |
[+, *] |
[a, b] |
|
( |
左括号,压栈 | [+, *, (] |
[a, b] |
|
c |
操作数,入队 | [+, *, (] |
[a, b, c] |
|
- |
栈顶是左括号,压栈 | [+, *, (, -] |
[a, b, c] |
|
d |
操作数,入队 | [+, *, (, -] |
[a, b, c, d] |
|
) |
右括号,弹出直到左括号 | [+, *] |
[a, b, c, d, -] |
弹出-,丢弃( |
/ |
栈顶*优先级 (3) = /(3),弹出*;栈顶+优先级 (2) < /(3),压栈 |
[+, /] |
[a, b, c, d, -, *] |
|
e |
操作数,入队 | [+, /] |
[a, b, c, d, -, *, e] |
|
^ |
栈顶/优先级 (3) < ^(4),压栈 |
[+, /, ^] |
[a, b, c, d, -, *, e] |
|
f |
操作数,入队 | [+, /, ^] |
[a, b, c, d, -, *, e, f] |
|
| 结束 | 弹出栈中所有元素 | [] |
[a, b, c, d, -, *, e, f, ^, /, +] |
依次弹出^、/、+ |
最终后缀式 :abcd-*ef^/+
三、特殊情况处理:一元负号
这是转换中最容易出错的地方。一元负号(如-a、3*-4)和二元减号(如a-b)符号相同,但优先级和结合性不同。
一元负号的判断规则
当-出现在以下位置时,是一元负号:
- 表达式的开头(如
-a+b) - 左括号的后面(如
a*(-b+c)) - 另一个运算符的后面(如
a+-b、a*-b)
一元负号的转换规则
- 优先级高于所有二元运算符
- 右结合
- 转换时,将
-放在操作数的后面,通常用特殊符号(如~)区分,避免与二元减号混淆
实例:转换表达式 -a + b * (-c - d)
- 第一个
-是一元负号,转换为a~ - 第二个
-是一元负号,转换为c~ - 处理括号内:
c~ - d→c~d- - 处理乘法:
b * c~d-→bc~d-* - 处理加法:
a~ + bc~d-*→a~bc~d-*+
3、触发器
①具有两个稳定的状态,分别用二进制数码的"1"和"0"来表示。
②由一个稳态到另一个稳态,必须有外界信号的触发,否则,它将长期处于某个状态,即长期保持所记忆的信息。
③具有两个输出端,即原码输出和反码输出,且两个状态要相反,否则会违反触发器的状态要求。
④触发器的基本组成是门电路,一般会使用基本的触发器构成集成触发器,在集成触发器中可以利用置位、复位功能来进
行触发器的状态设置,采用集成触发器可以设计和时序电路相关的中规模集成器件,如计数器、寄存器、信号发生器等。没有加法器,加法器是组合逻辑电路,不需要触发器,仅用与门、或门、非门等基本逻辑门即可实现
4、5 级流水线整体概述
一条指令的完整执行需要完成 "取指令→分析指令→执行指令→访问内存→保存结果" 这 5 个步骤,5 级流水线正好对应这 5 个步骤,每个阶段耗时 1 个时钟周期:
表格
| 阶段缩写 | 阶段名称 | 英文全称 | 核心任务 |
|---|---|---|---|
| IF | 取指阶段 | Instruction Fetch | 从内存中取出下一条要执行的指令 |
| ID | 译码阶段 | Instruction Decode | 分析指令功能,读取操作数 |
| EX | 执行阶段 | Execute | 执行算术逻辑运算,计算地址 |
| MEM | 访存阶段 | Memory Access | 访问数据存储器(读 / 写) |
| WB | 写回阶段 | Write Back | 将运算结果写回寄存器堆 |
4、I/O端口
一、存储器映射方式(MMIO,统一编址)
核心原理
将I/O 端口的地址映射到系统的内存地址空间 中,I/O 端口和内存单元使用同一个地址空间,没有任何区别。CPU 访问 I/O 端口的方式和访问普通内存完全相同,不需要专门的 I/O 指令。
| 优点 | 缺点 |
|---|---|
| ✅ 指令丰富:可以使用所有内存访问指令,支持复杂的寻址方式(如基址寻址、变址寻址),编程灵活 | ❌ 占用内存地址空间:I/O 端口会占用一部分宝贵的内存地址空间,32 位系统中这个问题比较突出 |
| ✅ 无需专门 I/O 指令:简化了 CPU 指令集设计,RISC 架构普遍采用这种方式 | ❌ 地址译码复杂:需要区分哪些地址是内存,哪些是 I/O,地址译码器逻辑更复杂 |
| ✅ 访问速度快:可以利用 CPU 的缓存机制(虽然 I/O 通常不缓存)和流水线优化 | ❌ 内存保护复杂:需要在内存管理单元(MMU)中专门设置 I/O 地址的访问权限 |
| ✅ 支持大容量 I/O:地址空间大小与内存相同,理论上可以支持无限多的 I/O 设备 |
二、I/O 映射方式(PMIO,独立编址)
核心原理
I/O 端口拥有完全独立于内存的地址空间 ,和内存地址空间是分开的、互不重叠的。CPU 需要使用专门的 I/O 指令来访问 I/O 端口,并且通过专门的硬件引脚来区分当前是访问内存还是访问 I/O
| 优点 | 缺点 |
|---|---|
| ✅ 不占用内存地址空间:I/O 地址空间完全独立,不会消耗宝贵的内存地址 | ❌ 指令有限:只有简单的输入输出指令,不支持复杂的寻址方式,编程不够灵活 |
✅ 地址译码简单 :通过M/IO#引脚可以直接区分内存和 I/O 访问,地址译码器逻辑简单 |
❌ 需要专门的 I/O 指令:增加了 CPU 指令集的复杂度 |
| ✅ 地址空间小:x86 的 I/O 地址空间只有 64KB(0000H~FFFFH),地址线只需要 16 根 | ❌ 访问速度慢:无法利用 CPU 的缓存和流水线优化,I/O 指令通常需要多个时钟周期 |
| ✅ 保护简单:I/O 端口的访问权限可以单独控制,与内存保护分开 | ❌ 容量有限:64KB 的地址空间最多只能支持 65536 个 8 位端口 |
5、DMA访问
| 编号 | 箭头方向 | 对应信号 | 说明 |
|---|---|---|---|
| (1) | DMAC → CPU | 总线请求 | DMAC 向 CPU 申请总线控制权 |
| (2) | CPU → DMAC | 总线响应 | CPU 同意交出总线控制权 |
| (3) | 设备 → DMAC | DMA 请求 | 外设向 DMAC 申请进行 DMA 传输 |
| (4) | DMAC → 设备 | DMA 响应 | DMAC 通知外设可以开始传输数据 |

6、RS485和RS422
1. 通信模式(最本质区别)
-
RS485:半双工通信
- 发送和接收共用一对差分线(A 和 B)
- 同一时刻只能有一个设备发送数据,其他设备只能接收
- 设备需要在发送模式和接收模式之间切换
- 类比:对讲机,同一时间只能一个人说话
-
RS422:全双工通信
- 有两对独立的差分线:一对用于发送(TX+、TX-),一对用于接收(RX+、RX-)
- 发送和接收可以同时进行,互不干扰
- 不需要切换模式,通信延迟更低
- 类比:打电话,双方可以同时说话
2. 接线方式
-
RS485:2 线制
- 仅需 A、B 两根线(通常加一根地线 GND)
- 所有设备的 A 端连在一起,B 端连在一起,形成一条总线
- 布线简单,成本低,适合远距离多设备组网
-
RS422:4 线制
- 需要 TX+、TX-、RX+、RX - 四根线(通常加一根地线 GND)
- 主机的 TX 接从机的 RX,主机的 RX 接从机的 TX
- 布线复杂,成本高,不适合多设备远距离组网
3. 拓扑结构与节点数量
-
RS485:总线型拓扑,多主多从
- 一条总线上可以连接最多 32 个标准节点(增强型芯片可支持 128 个甚至 256 个)
- 支持多主模式(多个设备可以主动发送数据),但实际应用中多采用主从模式
- 所有设备并联在总线上,新增设备只需并联到总线即可,非常灵活
-
RS422:点对多拓扑,单主多从
- 一条总线上只能有1 个发送器(主设备) ,最多可以有10 个接收器(从设备)
- 不支持多主模式,只有主设备可以主动发送数据
- 从设备之间不能直接通信,所有通信都必须经过主设备
7、I2C
IIC总线的主要特点包括:
③总线长度可以达到7.6m,并能够以100kb/s的最大传输速率支持40个组件。
④支持多主控,任何一个时间点只能有1个主控,需要通过总线仲裁来决定。
⑤IIC总线的协议层包含物理层和数据链路层。在物理层,IIC总线仅仅使用2条信号线:一条是串行数据线,
用于数据的接收和发送;另外一条是串行时钟线,用于指示何时数据线上是有效数据。在数据链路层,每个连接到
IIC总线上的设备都有唯一的地址,设备的地址由设计者来决定。主IIC设备发出时钟信号、地址信号和控制信号,
选择通信的从IIC设备和控制收发。
IIC总线有如下要求:
①各个节点设备必须具有IIC接口功能。
②各个节点设备必须共地。
③两根信号线必须接上拉电阻。
④ΠC总线上面的状态一般包括空闲状态、占有总线、释放总线等,包含了启动信号、停止信号、应答信号
等。