Loading... ## 计算机的工作过程 ![image-20240914164733718.png][1] ## 指令的基本格式 **指令** (又称**机器指令**) ,是指示计算机执行某种操作的命令,是计算机运行的**最小功能单位**。 一台计算机的所有指令的集合构成该机器的**指令系统**,也称为**指令集**。一台计算机只能执行自己指令系统中的指令,不能执行其他系统的指令。 ### 指令格式 一条指令就是机器语言的一个语句,它是一组有意义的二进制代码,通常要包含**操作码字段**和**地址码字段**两部分。 ![image-20240914161124649.png][2] ### 按地址码数目分类 一条指令可能包含多个地址码。根据地址码数目不同,可以将指令分为零地址指令,一地址指令,二地址指令等。 - **零地址指令**: 1. **不需要操作数**的指令,如空操作,停机,关中断等。 2. 两个操作数**隐含存放在栈顶和次栈顶**,且将计算结果压回栈顶。如:数据结构中的后缀表达式。 - **一地址指令**: 1. **只需要单操作数**的指令,如加一,减一,取反,求补等。 指令含义:$OP(A_1) \rightarrow A_1$ 。 完成一条指令需要三次访存:取指 $\rightarrow$ 读 $A_1 \ \rightarrow$ 写 $A_1$ 。 > 此处 $A_1$ 指某个主存地址,$(A_1)$ 表示 $A_1$ 所指向的地址中的内容。 2. 需要两个操作数,但其中一个操作数**隐含在某个寄存器中**。(如隐含在 ACC ) 指令含义:$(ACC) \ OP \ (A_1) \rightarrow ACC$ 。 完成一条指令需要两次访存:取指 $\rightarrow$ 读 $A_1$ 。 - **二地址指令**:常用于**需要两个操作数**的算术运算,逻辑运算等相关指令。 指令含义:$(A_1) OP (A_2) \rightarrow A_1$ 。 完成一条指令需要访存四次:取指 $\rightarrow$ 读 $A_1 \rightarrow$ 读 $A_2 \rightarrow$ 写 $A_1$ 。 - **三地址指令**:常用于**需要两个操作数**的算术运算,逻辑运算等相关指令,会显式指明写入的地址。 指令含义:$(A_1) OP (A_2) \rightarrow A_3$ 。 完成一条指令需要访存四次:取指 $\rightarrow$ 读 $A_1 \rightarrow$ 读 $A_2 \rightarrow$ 写 $A_3$ 。 - **四地址指令**:会指定下一条将要执行指令的地址。 指令含义:$(A_1) OP (A_2) \rightarrow A_3, A_4 = $ 吓一跳将要执行指令的地址。 完成一条指令需要访存四次:取指 $\rightarrow$ 读 $A_1 \rightarrow$ 读 $A_2 \rightarrow$ 写 $A_3$ 。和三地址的区别在于指令执行完后会将 PC 的值修改为 $A_4$ 所指的地址。 > $n$ 位地址码的直接寻址范围为 $2^n$ 。若指令总长度固定不变,则地址码数量越多,寻址能力越差。 ### 按指令长度分类 - **指令字长**:一条指令的总长度。 (可能会变,如半字长指令,单字长指令,双字长指令) 指令字长会影响取指令所需的时间。如:机器字长 = 存储字长 = 16bit ,则取一i套双字长指令需要两次访存。 - **机器字长**:CPU 进行一次正数运算所能处理的二进制数据的位数。(通常和 ALU 直接相关) - **存储字长**:一个存储单元中的二进制代码位数。(通常和 MDR 位数相同) **定长指令字结构**:指令系统中所指令的长度都相等。 **变长指令字结构**:指令系统中各种指令的长度不等。 ### 按操作码长度分类 - **定长操作码**:在指令字的最高位部分分配固定的若干位 (定长) 表示操作码,指令系统中所有指令的操作码长度都相同。控制器的译码电路设计简单,但灵活性较低。 - 一般 $n$ 位操作码字段的指令系统最大能够表示 $2^n$ 条指令。 - **优点**:定长操作码对于简化计算机硬件设计,提高指令译码和识别速度很有利。 - **缺点**:指令数量增加时会占用更多的固定位,留给表示操作数地址的位数受限。 - **可变长操作码**:全部指令的操作码字段的位数不固定,且分散地放在指令字地不同位置上。 - 控制器的译码电路设计复杂,但灵活性较高。 - **扩展操作码**:最常见的变长操作码,使操作码地长度随地址码地减少而增加,不同地址数地指令可以具有不同长度地操作码,从而在满足需要地前提下,有效的缩短指令字长。 - **优点**:在指令字长有限的前提下仍然保持比较丰富地指令种类。 - **缺点**:增加了指令译码和分析地难度,使控制器的设计复杂化。 ### 按操作类型分类 1. 数据传送 - LOAD:将**存储器**中的数据存放到**寄存器**中; - STORE :将**寄存器**中的数据放到**存储器**中。 2. 算术逻辑操作 算术:加减乘除,加一减一,求补,浮点运算,十进制运算等。 逻辑:与,或,非,异或,位操作,位测试,位清楚,位求反等。 3. 移位操作 算术移位,逻辑移位,循环移位 (带进位与不带进位) 4. 转移操作 - 无条件转移 JMP - 条件转移 JZ:结果为 0 ;JO :结果溢出;JC :结果有进位。 - 调用和返回 CALL 和 RETURN 。 - 陷阱 (Trap) 与陷阱指令。 5. 输入输出操作 CPU 寄存器与 IO 端口之间的数据传送 (端口即 IO 接口中的寄存器) 。 ### 扩展操作码 一扩展操作吗的设计示例:设指令字长为 16 位,每个地址码占 4 位。前四位位基本操作码字段 OP ,另有 3 各 4 位长的地址字段 $A_1, A_2, A_3$ 。 - 0000 ~ 1110 位三地址指令,有 15 条,1111 留作扩展操作码使用。 - 1111 0000 ~ 1111 1110 为二地址指令,有 15 条,1111 1111 六座扩展操作码使用。 - 以此类推,一地址指令有 15 条,零地址指令有 16 条。 ![image-20240915174916335.png][3] 在设计扩展操作码指令格式时,须注意以下两点: 1. 不允许短码是长码的前缀,即短操作码不能与长操作码的前缀相同。 2. 个指令的操作码一定不能重复。 通常情况下,对使用频率较高的指令,分配较短的操作码;使用频率较低的指令则分配较长的操作码,从而尽可能减少指令译码和分析的时间。 ![image-20240920143932164.png][4] 设地址长度为 $n$ 位,上一层留出 $m$ 种状态,下一层可扩展出 $m \times 2^n$ 种状态。 ## 指令寻址 一条指令由**操作码** (OP) 和**地址码** (可能有多个) 组成,那么如何确定下一条指令的地址呢? 通过前面计算机工作过程的图,可以知道 CPU 内部有一个很重要的寄存器 **PC (程序计数器)** ,用以指明下一条指令的存放地址。 ### 顺序寻址 > 取指令的时候同时进行 $(PC) + \text{``1"} \rightarrow PC$ 。此处的 1 指的是一个指令字长,实际加的值会因指令长度,编址方式而不同。 - 若系统采用**定长指令字结构**, 且指令字长 = 存储字长 = 2B - 主存按**字**编址的情况下,$(PC) + 1 \rightarrow PC$ 。 - 主存按**字节**编址的情况下,$(PC) + 2 \rightarrow PC$ 。 - 若系统采用**变长指令字结构**,且主存按**字节**编址,则寻址方式如下: 1. 读入一个字,根据操作码判断这条指令的总字节数 $n$ ,修改 $PC$ 的值:$(PC) + n \rightarrow PC$ 。 2. 根据指令的类型,CPU 可能海选哟进行多次方寸,每次读入一个字,并执行上述 1 的流程。 ![image-20240921121129196.png][5] 如上图黄色部分需要访存两次,灰色部分需要访存三次。 ### 跳跃寻址 一般情况下计算机都是顺序寻址,但某些转移指令会直接修改 $PC$ 的值。 设某系统采用**定长指令字结构**,指令字长 = 存储字长 = 2B ,且主存**按字编址**。 ![image-20240921121933428.png][6] 当顺序寻址到 $PC = 3$ 的时候,读取到无条件转移指令 $JMP$ ,此时需要将 $PC$ 中的内容直接修改为 $JMP$ 指令指明的地址 ,此处下一条指令应该从地址 7 继续执行。 ## 数据寻址 指令中的地址码一般都是形式地址,需要通过特定的方法转换成操作数的正是地址,即有效地址 (EA) 。常见的数据寻址方法如下: ![image-20240921134329007.png][7] 一般会在形式地址前设置几个寻址特征位,用于记录当前使用的是什么寻址方法。 ![image-20240921134509967.png][8] ### 直接寻址 指令字中的形式地址 $A$ 就是操作数的真实地址 $EA$,即 $EA = A$ 。 ![image-20240921135714589.png][9] - 访存次数:取指令访存一次,执行指令访存一次,暂不考虑存结果的访存。 - 优点:简单,指令执行阶段进访问一次主存,不需要专门计算操作数的地址。 - 缺点:$A$ 的位数决定了该指令操作数的寻址范围,操作数的地址不易修改,灵活性差。 ### 间接寻址 指令的地址字段给出的形式地址不是操作数的真正地址,而是操作数有效地址所在的存储单元的地址,也就是操作数地址的地址,即 $EA = (A)$ 。 ![image-20240921154830840.png][10] - 优点:可以扩大寻址范围,即有效地址 $EA$ 的位数大于形式地址 $A$ 的位数。 便于编写程序,用间接寻址可以方便地完成子程序返回。 - 缺点:指令在执行阶段需要多次访存。一次间接寻址需要两次访存,多次寻址则需要根据存储字的最高位确定几次访存。 ### 寄存器寻址 在指令字中直接给出操作数所在的寄存器编号,即 $EA = R_i$ ,其操作数在 $R_i$ 所指的寄存器内。 ![image-20240921155259656.png][11] - 指令执行只需要在取指令的时候访存一次。(不考虑存结果的访存) - 优点:指令在执行阶段不访问主存,只访问寄存器。指令字短且执行速度快,支持向量 / 矩阵运算。 - 缺点:寄存器个数有限,且造价昂贵。 ### 寄存器间接寻址 寄存器 $R_i$ 中给出的不是一个操作数,而是操作数所在主存单元的地址,即 $RA = (R_i)$ 。 ![image-20240921155046583.png][12] - 一次执行需要在取指令和执行指令的时候各访存一次,共两次。 - 特点:比一般间接寻址的速度快,但指令的执行阶段需要访问主存 (操作数在主存中) 。 ### 隐含寻址 不是显式地给出操作数地地址,而是在指令中隐含操作数地地址。 ![image-20240921155642246.png][13] - 优点:有利于缩短指令字长。 - 缺点:需要增加存储操作数或隐含地址地硬件。 ### 立即寻址 形式地址 $A$ 就是操作数本身,又称为立即数,一般采用补码形式。一般其特征码为 `#` 。 ![image-20240921160001584.png][14] - 执行过程中只需要在取指令的时候访存一次。 - 优点:指令执行阶段不访问主存,指令执行时间最短。 - 缺点:$A$ 的位数限制了立即数的范围。如 $A$ 的位数为 $n$ 时,克表示的数据范围为 $-2^{n-1} \sim 2^{n-1} - 1$ 。 ### 偏移寻址 接下来三个寻址方式,相对寻址,基址寻址,变址寻址,统称为偏移寻址。 #### 基址寻址 将 CPU 中**基址寄存器 (BR)** 的内容加上指令格式中的形式地址 $A$ ,从而形成操作数的有效地址,即 $EA = (BR) + A$ 。 ![image-20240921190400458.png][15] - 优点:用户不必考虑自己的程序存于主存的哪一块空间区域,便于程序 "浮动" (即程序可以放置于内存中的任意位置) ,方便实现**多道程序并发运行**。 可以扩大寻址范围,因为基址寄存器的位数大于形式地址的位数。 - 基址寄存器是**面向操作系统**的,其**内容由操作系统或管理程序确定**。在程序执行过程中,基址寄存器的内容不变 (作为基地址) ,形式地址可变 (作为偏移量) 。 - 当采用通用寄存器作为基址寄存器时,**可由用户决定使用哪个寄存器**作为基址寄存器,但其**内容仍由操作系统决定**。 #### 变址寻址 有效地址 $EA$ 等于指令字中的形式地址 $A$ 与变址寄存器 $IX$ 的内容相加之和,即 $EA = (IX) + A$ 。其中 $IX$ 可为专用的变址寄存器,也可使用通用寄存器。 ![image-20240921192053029.png][16] - 变址寄存器是面向用户的, 在程序执行过程中,变址寄存器的内容可由用户改变 ($IX$ 作为偏移量) ,形式地址 $A$ 不变 (作为基地址) 。与基址寻址刚好相反。 - 优点:在数组处理的过程中,可设定 $A$ 为数组的首地址,不断改变变址寄存器 $IX$ 的内容,便可很容易得到数组中任一数据的地址,特别**适合编写循环程序**。 ![image-20240921234718209.png][17] #### 相对寻址 把程序计数器 $PC$ 的内容加上指令格式中的形式地址 $A$ 而形成操作数的有效地址,即 $EA = (PC) + A$ ,其中 $A$ 是相对于 $PC$ 所指地址的偏移量,通常用补码表示,可正可负。 ![image-20240921235548974.png][18] 注意 $PC$ 在执行指令之前会先自增,因此计算相对寻址要使用自增后的地址,也就是 $PC$ 中的地址来计算,而不是该条指令所在的地址。 - 优点:操作数的地址不是固定的,它随着 $PC$ 值得变化而变化,且与指令地址之间总是相差一个固定值,因此**便于程序浮动**。(一段代码在程序内部得浮动) 相对寻址广泛应用于**转移指令**。 ### 堆栈寻址 操作数存放在堆栈中,隐含使用堆栈指针 $SP$ 作为操作数的地址。堆栈可以在存储器中 (软堆栈) ,或使用专门的寄存器组 (硬堆栈) 。 ![image-20240922013256541.png][19] ### 复合寻址 实际应用中往往需要多种寻址方式复合使用,例如基址和变址复合寻址: ![image-20240921235112591.png][20] > 可以理解为复合函数 ### 小结 | 寻址方式 | 有效地址 | 访存次数 (指令执行期间) | | ------------------ | --------------------------------- | ---------------------------- | | 隐含寻址 | 程序指定 | 0 | | 立即寻址 | $A$ 即是操作数 | 0 | | 间接寻址 | $EA = A$ | 1 | | 一次间接寻址 | $EA = (A)$ | 2 | | 寄存器寻址 | $EA = R_i$ | 0 | | 寄存器间接一次寻址 | $EA = (R_i)$ | 1 | | 相对寻址 | $EA = (PC) + A$ | 1 | | 基址寻址 | $EA = (BR) + A$ | 1 | | 变址寻址 | $EA = (IX) + A$ | 1 | | 堆栈寻址 | 入栈 / 出栈时 $EA$ 的确定方式不同 | 硬堆栈不访存,软堆栈访存一次 | ## 程序的机器级代码 > x86 架构是 32 位的 ![image-20240922185547726.png][21] ### 常用寄存器 ![image-20240922184550298.png][22] - 变址寄存器可用于线性表,字符串的处理,知道这是两个寄存器就行。 - 若将寄存器前面的 $E$ 去掉,则表明仅使用低 16 位。(64 位的机器寄存器通常以 $R$ 开头) ### 内存读写长度 - `dword ptr` :双字,32bit - `word ptr` :单字,16bit - `byte ptr` :字节,8bit 如 `mov byte prt [af996h], 5` 意思是将立即数 $5$ 复制到内存地址 `af996h` 所指的一字节中。 ### 常见汇编指令 #### 算数和逻辑指令 | 功能 | 汇编指令 | 注释 | | ------ | ------------------------------------ | ------------------------------------------------------------ | | 加 | `add, des, src` | 计算 `des + src` 并将结果存入 `des` | | 减 | `sub des, src` | 计算 `des - src` 并将结果存入 `des` | | 乘 | `mul des, src` <br />`imul des, src` | 无符号数 `des * src` 并将乘积存入 `des`<br />有符号数 `des * src` 并将乘积存入 `des` | | 除 | `div src` <br />`idiv, src` | 无符号数除法 `edx:eax/src` ,商存入 `eax` ,余数存入 `edx`<br />有符号数除法 `edx:eax/src` ,商存入 `eax` ,余数存入 `edx` | | 取负数 | `neg des` | 将 `dex` 取负数并将结果存入 `des` | | 自增 | `inc des` | 将 `des++` 并将结果存入 `des` | | 自减 | `dec des` | 将 `des--` 并将结果存入 `des` | | 与 | `and des, src` | 将 `des, src` 逐位相与,结果存回 `des` | | 或 | `or des, src` | 将 `des, src` 逐位相或,结果存回 `des` | | 非 | `not des` | 将 `des` 逐位取反,结果存回 `des` | | 异或 | `xor des, src` | 将 `des, src` 逐位异或,结果存回 `des` | | 左移 | `shl des, src` | 将 `des` 逻辑左移 `src` 位,结果放回 `des` (通常 `src` 是常量) | | 右移 | `shr des, src` | 将 `des` 逻辑右移 `src` 位,结果放回 `des` (通常 `src` 是常量) | - `edx:eax` 表示同时使用两个寄存器,因为除法运算之前需要进行位扩展,且除法运算的被除数默认已经在寄存器内了。 - x86 不允许两个操作数同时来自主存。 #### 比较指令 ```assembly cmp a, b # 比较 a 和 b 两个数 ``` `cmp a, b` 本质上是进行 `a - b` 减法运算,并生成标志位 $OF, ZF, CF, SF$ ,然后条件转移指令根据相应的标志位进行条件判断。 #### 跳转指令 - 无条件跳转:`jmp des` 这里的 `des` 可以使用常数,寄存器的值,主存的值,或者标号 ```assembly mov eax, 7 mov ebx, 6 jmp NEXT mov ecx, ebx NEXT: # 用标号锚定位置 mov ecx, eax ``` - 条件跳转: ```assembly je des # jump when equal, 若 a == b 则跳转 jne des # jump when not equal, 若 a != b 则跳转 jg des # jump when greater than, 若 a > b 则跳转 jge des # jump when greater than or equal to, 若 a >= b 则跳转 jl des # jump when less than, 若 a < b 则跳转 jle des # jump when less than or equal, 若 a <= b 则跳转 ``` > 标号可以是函数名,对应的是函数的起始地址。 ### AT&T 格式 | | AT&T 格式 | Intel 格式 | | --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | 目的操作数 `des` ,源操作数 `src` | `op src, des`<br />源操作数在左,目的操作数在右 | `op des, src`<br />源操作数在右,目的操作数在左 | | 寄存器的表示 | `mov %ebx, %eax`<br />寄存器名前要加 `%` | `mov eax, ebs`<br />直接写寄存器名即可 | | 立即数的表示 | `mov $985, %eax`<br />立即数之前要加 `$` | `mov eax, 985`<br />直接写数字即可 | | 主存地址的表示 | `move %eax, (af996h)`<br />用小括号 | `mov [af996h], eax`<br />用中括号 | | 读写长度的表示 | `moveb $5, (af996h)`<br />`movw $5, (af996h)`<br />`movl $5, (af996h)`<br />`addb $4, (af996h)`<br />指令后加 `b, w, l` 分别表示读写长度 | `move byte ptr [af996h], 5`<br />`move word ptr [af996h], 5`<br />`move dword ptr [af996h], 5`<br />`add byte ptr [af996h], 4`<br />在主存地址前说明读写长度 | | 主存地址偏移量的表示 | `movl -8(%ebx), %eax`<br />偏移量 (基址) <br />`movl 4(%ebx, %eax, 32), %eax`<br />偏移量 (基址, 变址, 比例因子) | `mov eax, [ebx - 8]`<br />[基址 + 偏移量]<br />`mov eax, [ebx + ecx*32 + 4]`<br />[基址 + 变址*比例因子 + 偏移量] | ### 选择语句 > 程序计数器 $PC$ 又可以被称为 $IP$ (Instruction Pointer) 选择语句的机器级表示:如以下的 C 语言代码 ```c if (a > b) c = a; else c = b; ``` 转换为汇编为 ```assembly mov eax, 7 # 假设变量 a = 7 ,存入 eax mov ebx, 6 # 假设变量 b = 6 ,存入 ebx cmp eax, ebx jle NEXT mov ecx, eax # 假设用 ecx 存储变量 c jmp END NEXT: mov ecx, ebx END: ``` ### 循环语句 C 语言代码如下: ```c int result = 0; for (int i = 1; i <= 100; i++) { result += i; } ``` 使用条件转移指令实现: ```assembly mov eax, 0 # 使用 eax 保存 result ,初始化为 0 mov edx, 1 # 使用 edx 保存 i ,初始值为 1 cmp edx, 100 jg L2 L1: add eax, edx inc edx cmp edx, 100 jle L1 L2: ``` 此外,汇编中还有一个 `loop` 指令可以用来实现循环。比如以下高级语言 ```c for (int i = 500; i > 0; i--) { // do something } ``` 使用 `loop` 实现如下 ```assembly mov ecx, 500 # loop 指令规定使用 ecx 作为循环计数器,不可改为其他通用寄存器 Looptop: # do something loop Looptop # ecx--, 若 ecx != 0 ,跳转到 Looptop ``` 理论上,能用 `loop` 指令实现的功能一定能用条件转移指令实现。使用 `loop` 指令的原因是会使代码更简洁清晰。 补充:`loopxx` 指令 - `loopnz` :当 `ecx != 0 && ZF == 0` 时,继续循环。 - `loopz` :当 `ecx != 0 && ZF == 1` 时,继续循环。 > 知道是循环指令即可 ### 函数调用 函数的**栈帧 (Stack Frame)** :保存函数内定义的**局部变量**,保存**函数调用相关的信息**。 ![image-20240923010322201](指令系统.assets/image-20240923010322201.png) #### 函数调用指令 - `call` 指令:`call <function_name>` 1. 将 $IP$ 旧值压栈保存 (保存在函数的栈帧顶部) 。 2. 设置 $IP$ 新值,无条件跳转至被调用函数的第一条指令。 - `ret` 指令:从函数栈帧顶部找到 $IP$ 旧值,将其出栈并恢复 $IP$ 寄存器。 #### 栈帧内的数据访问 前面寄存器的部分提到了两个寄存器:堆栈基指针 $EBP$ 和堆栈顶指针 $ESP$ : ![image-20240923010850223.png][23] 其中 $EBP$ 指向当前栈帧的 "底部" ,$ESP$ 指向当前栈帧的 "顶部" 。对栈帧内数据的访问,都是基于 $EBP$ 和 $ESP$ 进行的。 > x84 系统中,默认以 **4 字节**为栈的操作单位。 ##### 入栈出栈指令 入栈和出栈指令分别由 `push` 和 `pop` 两条汇编指令实现。指令格式如下: ```assembly push a # 先让 esp 减 4 ,再将 a 压入 pop b # 栈顶元素出栈并写入 b ,再让 esp 加 4 ``` 其中 `a` 可以是立即数,寄存器,主存地址;`b` 可以是寄存器或主存地址。 例如: ```assembly push eax # 将寄存器 eax 的值压栈 push 985 # 将立即数 985 压栈 push [ebp + 8] # 将主存地址 [ebp + 9] 里的数据压栈 pop eax # 栈顶元素出栈,写入寄存器 eax pop [ebp + 8] # 栈顶元素出栈,写入主存地址 [ebp + 8] ``` ##### 随机访问栈帧数据 使用 `push, pop` 只能访问栈顶元素,想要访问栈帧内任意一个元素可以使用 `mov` 指令,结合 `esp, ebp` 实现随机访问。 若想往栈帧中插入或删除值,可以使用 `add, sub` 指令修改 `esp` 的值。例如: ```assembly sub esp, 12 # 栈顶指针 -12 mov[esp+8], eax # 将 eax 的值复制到主存 [esp + 8] mov[esp+4], 958 # 将 985 复制到主存 [esp + 4] moveax, [ebp+8] # 将主存 [ebp + 8] 的值复制到 eax mov[esp],eax # 将 eax 的值复制到主存 [esp] add esp, 8 # 栈顶指针 +8 ``` #### 切换栈帧 ##### 调用切换 **函数调用**的时候,会执行一次 `push ebp` 指令,保存当前 `ebp` 的值;之后再执行一次 `mov ebp, esp` 指令,设置新的 `ebp` 为 `esp` 的值。 ![image-20240923013103138.png][24] > 上面的 `push ebp` 和 `move ebp, esp` 指令可以简化为一条 `enter` 指令。 ##### 返回切换 **函数返回**的时候,需要先执行 `mov esp, ebp` ,将 `esp` 指向当前栈帧的底部;然后再执行 `pop ebp` ,恢复之前记录的 `ebp` 。 上述操作可以简化为一条 `leave` 指令。 ![image-20240923013609939.png][25] 上面最后还有一条 `ret` 指令,其作用为从函数的栈帧顶部找到 `IP` 旧值,将其出栈并恢复 `IP` 寄存器。 ##### 总结 ![image-20240923013902991.png][26] > 除了 `main` 函数,其他所有函数的汇编代码结构都一样。 #### 栈帧内的内容 - 栈帧最底部一定是上一层栈帧基址 (`ebp` 旧值) 。 - 栈帧最顶部一定是返回地址 (当前函数的栈帧除外) 。 - 通常将**局部变量**集中存储在**栈帧底部**区域。 - 通常将**调用参数**集中存储在**栈帧顶部**区域。 - `gcc` 编译器将每个栈帧大小设置为 16B 的整数倍 (当前函数的栈帧除外) ,因此栈帧内**可能出现空闲未使用**的区域。 ![image-20240923014648181.png][27] #### 总结 ![image-20240923015409674.png][28] ### 考点 - 只需要关注 x86 汇编语言,若考察其他汇编语言题目会详细注释。 - 题目给出某段简单程序的 C 语言,汇编语言,机器语言表示,能**结合 C 语言看懂汇编语言的关键语句**。(看懂常见指令,选择结构,循环结构,函数调用) - 汇编语言,机器语言一一对应,要**能结合汇编语言分析机器语言指令的格式,寻址方式**。 - **不会**考将 C 语言人工翻译为汇编语言或机器语言。 ![image-20240923015443605.png][29] ## CISC 和 RISC **CISC**:Complex Instruction Set Computer - **设计思路**:一条指令完成一个复杂的基本功能。 - **代表**:x86 架构,主要用于笔记本,台式机等。 **RISC**:Reduced Instruction Set Computer - **设计思路**:一条指令完成一个基本 "动作" ,多条指令组合完成一个复杂的基本功能。 - **代表**:ARM 架构,主要用于手机,平板电脑等。 > 80-20 规律:典型程序中 80% 的语句仅使用处理机中 20% 的指令 比如设计一套能实现整数、矩阵加 / 减 / 乘运算的指令集: - CISC的思路:除了提供整数的加减乘指令除之外,还提供矩阵的加法指令、矩阵的减法指令、矩阵的乘法指令,且每条指令由一个专门的电路完成。 有的复杂指令用纯硬件实现很困难,可以采用 "存储程序" 的设计思想,即由一个比较通用的电路配合存储部件完成一条指令。 - RISC的思路:只提供整数的加减乘指令 一条指令一个电路,电路设计相对简单,功耗更低。方便实现 “并行” 和 “流水线” 技术 | | CISC | RISC | | ---------------- | ------------------------------------ | ------------------------------------ | | 指令系统 | 复杂,庞大 | 简单,精简 | | 指令数目 | 一般大于 200 条 | 一般小于 100 条 | | 指令字长 | 不固定 | 定长 | | 可访存指令 | 不加限制 | 只有 `Load/Store` 指令 | | 各种指令执行时间 | 相差较大 | 绝大多数在一个周期内完成 | | 各种指令使用频度 | 相差很大 | 都比较常用 | | 通用寄存器数量 | 较少 | 多 | | 目标代码 | 难以用优化编译生成搞笑的目标代码程序 | 采用优化的编译程序,生成代码较为高效 | | 控制方式 | 绝大多数为微程序控制 | 绝大多数为组合逻辑控制 | | 指令流水线 | 可以通过一定方式实现 | 必须实现 | [1]: https://blog.domineto.top/usr/uploads/2024/09/3094787678.png [2]: https://blog.domineto.top/usr/uploads/2024/09/3756175207.png [3]: https://blog.domineto.top/usr/uploads/2024/09/3131280045.png [4]: https://blog.domineto.top/usr/uploads/2024/09/1678782369.png [5]: https://blog.domineto.top/usr/uploads/2024/09/3597613746.png [6]: https://blog.domineto.top/usr/uploads/2024/09/1918740906.png [7]: https://blog.domineto.top/usr/uploads/2024/09/387391920.png [8]: https://blog.domineto.top/usr/uploads/2024/09/2418991533.png [9]: https://blog.domineto.top/usr/uploads/2024/09/987564406.png [10]: https://blog.domineto.top/usr/uploads/2024/09/3301711995.png [11]: https://blog.domineto.top/usr/uploads/2024/09/23561201.png [12]: https://blog.domineto.top/usr/uploads/2024/09/288043752.png [13]: https://blog.domineto.top/usr/uploads/2024/09/3082671893.png [14]: https://blog.domineto.top/usr/uploads/2024/09/2625439787.png [15]: https://blog.domineto.top/usr/uploads/2024/09/3428912030.png [16]: https://blog.domineto.top/usr/uploads/2024/09/825964966.png [17]: https://blog.domineto.top/usr/uploads/2024/09/3103606385.png [18]: https://blog.domineto.top/usr/uploads/2024/09/1910573853.png [19]: https://blog.domineto.top/usr/uploads/2024/09/4035078890.png [20]: https://blog.domineto.top/usr/uploads/2024/09/2745816558.png [21]: https://blog.domineto.top/usr/uploads/2024/09/66890326.png [22]: https://blog.domineto.top/usr/uploads/2024/09/821901005.png [23]: https://blog.domineto.top/usr/uploads/2024/09/1374568262.png [24]: https://blog.domineto.top/usr/uploads/2024/09/1424886926.png [25]: https://blog.domineto.top/usr/uploads/2024/09/2285314610.png [26]: https://blog.domineto.top/usr/uploads/2024/09/1380972700.png [27]: https://blog.domineto.top/usr/uploads/2024/09/3236898746.png [28]: https://blog.domineto.top/usr/uploads/2024/09/2199950742.png [29]: https://blog.domineto.top/usr/uploads/2024/09/1674351069.png 最后修改:2024 年 09 月 23 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏