1 指令格式
【补充知识】
计算机语言的层级关系:机器语言 - 汇编语言 - 高级语言
; 机器语言用二进制来编写,用来反映硬件的语言。但是二进制编写太难了,就诞生了汇编语言,对其编程进行封装、简化;
计算机工作的本质就是连续执行一条条机器语言,每一条机器语言的语句 称为 ==机器指令,==是一组有意义的二进制代码;
将全部机器指令的集合 称为 机器的 指令系统 ;
故,机器的指令系统 集中反映了 机器的功能 ;
1.1 指令的基本格式
1. 指令的定义( 地址码 + 操作码 )
intel是x86架构;手机是ARM架构---这也就导致了pc端上的软件不能直接再手机上运行 因为架构是不一样的 对应的指令系统也不一样!
地址码 指明 操作对象的位置,对谁进行操作 ?
指令的 地址码字段 用来指出该指令的源操作数的地址(一个或两个)、结果的地址以及一条指令的地址;
指令按照 地址码数目的不同,分为:零地址指令、一地址指令、二地址指令……
操作码 反映 要进行的操作,用户要干什么 ?
指令按照 操作码字段 的长度是否可变,分为:定长操作码、可变操作码、扩展操作码……
2. 指令 - 按 地址码 数量分类
① 零地址码
每一个操作数就是存放在主存单元中;每一个操作符对应的是一个零地址的指令,也在内存中;
从左往右扫描后缀表达式 操作数压入栈中 遇到操作符就出栈两个进行运算后再将结果压入栈中。
② 一地址指令
3次访存:取出指令;根据A1的内容 读出A1所指向的主存单元取出相应的数据,对这个数据执行op操作,得到运算结果放回A1
2次访存:因为放回ACC不需要访存【ACC就在cpu(运算器)里】;ACC里的内容和A1里内容执行op 然后放入ACC
③ 二、三地址指令
④ 四地址指令
3. 指令 - 按指令长度分类
存储字长和机器字长是固定不变的,但是指令字长是可变的
机器字长:CPU进行一次整数运算所能处理的二进制数据的位数
- CPU总线的宽度=运算器的位数=通用寄存器的宽度=数据总线宽度
- 计算机能直接处理的二进制数据的位数。机器字长通常与主存单元的位数一致。计算机中运算器进行算术运算和逻辑运算。机器字长也就是运算器进行定点数运算的字长,通常也是CPU内部数据通路的宽度。
- 机器字长反映了计算机的运算精度,即字长越长,数的表示范围也越大,精度也越高。机器的字长也会影响机器的运算速度。倘若CPU字长较短,又要运算位数较多的数据,那么需要经过两次或多次的运算才能完成,这样势必影响整机的运行速度。
机器字长与主存储器字长通常是相同的,但也可以不同。不同的情况下,一般是主存储器字长小于机器字长,例如机器字长是32位,主存储器字长可以是32位,也可以是16位,当然,两者都会影响CPU的工作效率。
字:字是指在计算机中作为一个整体被存取、传送、处理的一组二进制数。它代表计算机处理指令或数据的二进制数位数,字长是由CPU的类型所决定,不同的计算机系统的字长是不同的,常见的有8位、16位、32位、64位等,字长越长,计算机一次处理的信息位就越多,精度就越高,
- 注意字与字长的区别,字是单位,而字长是指标。
存储字长:一个存储单元中二进制代码的位数(通常和MDR位数相同)
- 存储字长不一定与机器字长相同(这也就出现了后面的主存容量扩展的问题(位扩展)),具体需要看机器的编址方式。一般情况下都是存储字长小于等于机器字长,即存储字长小于等于数据总线位数。
- 存储字长一般不固定的,固定的是每个字里面的字节大小(1字节=8bit)
数据字长:计算机数据存储所占用的位数。
存储单元:存放存储字或存储字节的主存空间被称为存储单元或主存单元。是CPU访问存储器的最小单位。因此每个存储单元都有对应的地址。至于具体存放什么这是由计算机的结构确定的。存储字或存储字节也可以理解为存储元集合,因此,存储单元也可以说是存放一个机器字的所有存储元集合(一般情况下存储字长等于机器字长)
存储元:存储元指的是存放一个二进制位(1或0)的基本器件(或电路)也被叫做存储元
- 由若干个存储元组成一个存储单元,然后再由许多存储单元组成一个存储器。
存储容量:对于字节编址的计算机,以字节数来表示存储容量;对于字编址的计算机,以字数与其字长的乘积来表示存储容量。
即存储容量=存储单元个数∗存储字长(或=存储单元个数∗存储字长/8)存储容量=存储单元个数∗存储字长(或=存储单元个数∗存储字长/8)
或字数(存储单元个数)=存储容量/字长字数(存储单元个数)=存储容量/字长
指令字长:一条指令的总长度,指令字长取决于操作码、操作码地址的长度和操作码地址的个数。
- 半字长指令、单字长指令、双字长指令 ——指令字长是机器字长的多少倍,如双字长指令:指令字长为机器字长的两倍
- 指令字长会影响取指时间,如:机器字长=存储字长=16bit,则取一条双字长指令需要两次访存操作
数据线:数据总线位数代表cpu单次交换数据量。数据线的条数=机器字长的位数
(注意
1:MDR与数据总线宽是不一定一样的,只与数据的个数有关,且和存储字长位数一样,如果数据线宽度和MDR不一样,那么就代表传一个数据要传多次。
评论有句生动形象的描述:“高速路上的车流量(MDR:位于CPU内)和出高速路口的车流量(总线:位于CPU外)是不一样的”
2:存储字长一般小于等于数据线宽度
)
存储器数据寄存器 MDR(Memory Data Register-MDR (或MBR) ):用于存放主存单元中的数据的寄存器
4. 指令 - 按操作码长度分类
扩展操作码 将在 4.1.2 扩展操作码指令格式 里详细介绍;
① 定长操作码
对于 操作码长度固定 编码方式的指令来说,操作码全部集中在操作码字段;
这种格式指令 ==译码时间短,==常用于 字长较长指令 的情况;
② 可变长操作码
对于 操作码长度可变 编码方式的指令来说,操作码分散在指令的不同字段中;
这种格式指令 ==压缩了操作码的平均长度,==常用于 字长较短 的计算机中;
但是 不固定 的特性会增加指令译码和分析的难度,是控制器设计更复杂;
5. 指令 - 按操作类型分类
4 实现的是程序执行流的变化 本来程序是一条一条执行的 但难免会遇到if else某些函数调用之类的,这样的情况下程序的执行流可能不是顺序的 可能会发生跳转
小结
1.2 扩展操作码指令格式
1. 扩展操作码原理
扩展操作码 就是 从操作码角度 对指令系统做 优化;
核心思想:指令位数固定时,地址码位数减少,则操作码可以利用的位数增加;
所谓的指令优化,就是在不添加指令字长的情况下,通过对地址码字段空闲区域的利用,达到扩展指令表达信息数量的效果;
【补充】
地址码的优化 主要通过 寻址技术 来介绍,先不在这里赘述;
前4位不全为1:三地址;
前四位全为1且再四位不全为1:二地址;
前8为全为1且再四位不全为1:一地址;
前12位全为1:0地址
2. 扩展操作码注意事项
哈夫曼树又称最优二叉树,是带权路径长度(WPL)最短的树,可以构造最优编码,用于数据传输,数据压缩等方向
前缀编码:
如果在一个编码方案中,任何一个编码都不是其他任何编码的前缀(最左子串),则称该编码是前缀编码。
前缀编码作用 :
前缀编码可以保证对压缩文件进行解码时不产生二义性,确保正确解码。
哈夫曼编码的主要思想:
在进行数据压缩时,为了使压缩后的数据文件尽可能短,可以采用 不定长编码。eg:
比如文字内容”ABCDEF”,通过二进制数据表示
传输数据为:“000001010011100101”按照3位一分来译码即可,但可以想象假如文字多了,数据量也是相当的大,而且某写字出现的频率都是不同的“中文的,了,….”频率大
所以需要前缀编码来进行编码(哈夫曼思想)
前缀编码:设计长短不等的编码,必须是任一字符的编码都不是另一个字符编码的前缀,这种编码称为前缀编码
因为每个字母的出现频率是不同的,我们假设给每个字母分配权值:A:27,B:8,C:15,D:15,E:30,F:5,首先按照它们的权值进行构造哈夫曼树
将所有权值左分支改为0,右分支改为1.
得到相应字符的的传输数据
下面是通过哈夫曼编码得到,可以看出,频率高的字符数据变的少,这样如果在很多字符下,会大大减少存储率和传输成本
- 原编码二进制串:000001010011100101
- 新编码二进制串:01100110100111000
3. 扩展操作码举例
CPU如何解析:检测1!跟之前一样 只是检测1的个数不一样
蓝色字是可能遇到的计算题!
小结
2 指令的寻址方式
【问题】寻址方式是什么 ?
答:指如何确定 本条指令 的 操作数地址 以及 下一条执行指令 的地址;
寻址方式的分类:
- 指令寻址
- 数据寻址(重点)
2.1 指令寻址
目前计算机有三种编址方式
(1)按位编址 :1b
(2)字节编址 :1 Byte=8b
(3)字编址:机器字长word=32b/64b
在计算机系统中,总线以固定大小的一块数据进行传输,这一块的数据也称为字(word),如今的计算机一般是32位和64位的,这里的位数则是指总线进行数据传输时一块数据的内存大小,也就是前面字的大小。
32位的计算机中:32位(bit)=4字节(byte)=1字(word)
64位的计算机中:64位(bit)=8字节(byte)=1字(word)存放一个机器字的存储单元,通常称为字存储单元,相应的单元地址叫字地址。
存放一个字节的存储单元,称为字节存储单元,相应的地址称为字节地址。
如果计算机中可编程的最小单位是字存储单元,则该计算机称为按字寻址的计算机。
如果计算机中可编程的最小单位是字节,则该计算机称为按字节寻址的计算机。
一个机器字可以包含数个字节,所以一个存储单元也可以包含数个能够单独编制的字节地址。eg:计算机字长(word)为32位,存储容量为16MB,按字节编址和字编制的时候,它的寻址范围分别为多少?
解:(1)按字节编址:1Byte=8b、16MB=2^24B=2^24*8b=2^27b (因为1MB=2^20B)
所以寻址范围:(2^27b)/(8b)=2^24b= 16M
(2) 按字编址: 字长为32b
所以寻址范围:(2^27b)/(32b)=2^22b= 4M
总结:由例题可以看出求按字节编址时机器字长完全不影响解题,字长只有按字编址时才用得上,所以我们一定要看清是哪种编址方式。
在这个例子里:给主存的这些存储单元编址的时候是按存储字 按字编址的 每个存储字可以存储两个字节的数据 而所有的这些指令刚好也占两个字节 因此只需要简单的让PC+1到下个指令
如果是按字节编址 pc+2 才可以 如果是变长指令字结构就更复杂了!就不是简单的+1-1能实现的了
1. 什么是指令寻址 ?
指令寻址,顾名思义,找的是 ==指令,==就是为了找到 下一条欲执行指令 的 存放位置 ;
很重要一点,程序计数器PC 就是用来存放下一条指令的地址,不过一断电就会清零;
【问题】那PC是如何得到程序计数器下一条指令的地址呢 ?
答:如果采用 ==顺序寻址,==很简单
PC = (PC) + n
(没加括号的PC表示下一条指令地址,加了括号表示当前指令地址) n的值取决于 ==指令字长 和 按字 / 字节编址,==可以看看下图的例子;
一个字节肯定是等于8bit,给了字的长度是16bit 就可以算出 这个字里有 2个字节!
2. 顺序寻址
下图假设系统采用 ==变长指令字结构 + 按字节编址,==注意图中各条指令分布;
顺序寻址中,
PC = (PC) + 1
,这个 1 指的是 1个指令字长,指令字长会根据 指令长度、编址方式 而变;
3. 跳跃寻址
指令除了顺序执行,当遇到 转移指令 时,就会发生 跳序执行;
跳跃寻址,下一条欲执行指令的地址 由转移指令指出;
到第三行之前就是顺序寻址的方式,到第三行JMP就无条件的jump到给的7号指令
下图,当指令 顺序执行 到
JMP 7
时,PC的值就会变成 7,这就是跳跃寻址的特点; 关于跳跃寻址中 ==转移地址的形成方式,==将在 偏移寻址 中学习;
小结
2.2 数据寻址
1.指令寻址vs数据寻址
第一个例子里JMP对应的地址码就是真实的地址 但是第二个就不是了 第三个的pc指向104 从pc指向的地址移动3
1. 数据寻址
在正式学习 常见的数据寻址 之前,要先带着一下几个要求学习:
- 要重点关注每种寻址方式的 有效地址;
- 搞清楚每种寻址方式的 操作数 在哪;
- 如何把数据寻址与指令优化联系起来;
【问题】寻址特征 字段是干什么的?
答:数据寻址方式有很多种,所以在指令字中必须设值一个字段,用来指明当前指令使用哪一种数据寻址;
【问题】有效地址EA 和 形式地址A
答:指令中的地址码字段通常都不是操作数的 真实地址,而是称为 形式地址,记为
A
; 操作数的真实地址称为 有效地址,记为
EA
,它是由 寻址方式 和 形式地址 共同来确定的; 举例:c = a + b;
2. 常见的数据寻址(6种)
时刻注意一点,一条指令的执行分为 取指令 和 执行指令 两个阶段;
① 直接寻址
直接寻址,有效地址由形式地址直接给出,
EA = A
; 也就是说,形式地址所指的地址 就是操作数存放的真实地址;
特点:
- 指令执行阶段,仅访存一次,用来取操作数;
- A的位数 限制了 指令操作数的 寻址范围 ;
② 间接寻址
间接寻址,有效地址由形式地址间接给出,
EA = (A)
(给 A 加括号,表示对A访存) 也就说,操作数的真实地址存放在A所指向的空间;
同理,还可以进行 多次间接寻址 ,不过需要额外占用 一位来充当 标识位 ,记录是否找到 有效地址 ;
【举例】有效地址位数为16 主存的存储单元是32位 则:
- 直接寻址 寻址范围为: 0~ 2^16-1
- 一次间址 寻址范围为:0~ 2^32-1
对于 一次间址 ,指令指向阶段需要两次访存:
- 第一次访存,找有效地址;
- 第二次访存,找操作数;
特点:
- 扩大寻址范围 (操作码较短时,又想要访存较远范围的操作数)
- 便于编制程序
- 指令执行阶段需要多次访存(时间换空间)
③ 寄存器寻址
寄存器直接寻址,有效地址 即 寄存器编号 ,EA = Ri ;
也就是说,此时的 形式地址存放的是 存储器编号,操作数就在对应的寄存器中;
特点:
- 操作数不在主存,故指令执行阶段 无需访存 ;
- 寄存器 (SRAM) 速度远大于 内存 (DRAM),故 执行速度快 ;
- 寄存器贵,那么个数一定不多,所以 寄存器寻址 适合 指令字短 的情况;
寄存器很少 所以很少的比特位就能表示寄存器的编号
④ 寄存器间接寻址
寄存器间接寻址,有效地址 存放在 形式地址所指的寄存器,EA = (Ri) ;
也就是说,形式地址存放寄存器的编号,真实地址存放在寄存器中;
特点:
- 指令的执行阶段需要访存,但比 间接寻址 少一次访存;
- 便于编制循环程序;
小总结(直接、间接、寄存器直接、寄存器间接):
- 间接比直接,表示范围大、速度慢;
- 寄存器间接,表示范围大,因为寄存器快且少访存一次,但是寄存器贵;
⑤ 隐含寻址
隐含寻址,操作数地址 隐含在 操作码 或 寄存器(比如之前的ACC)中;
隐含寻址应该是特定用于两个操作数的时候,指令的地址码存放其中一个操作数的真实地址,这一部分和 直接寻址 一样;
但另一个操作数会直接存放在某个寄存器中,然后直接输入到ALU中;
所谓隐含,就是说,其中一个操作数(或操作数地址)根本就不在指令中,而是 约定地 隐含在 ACC (累加器) ;
特点:
- 因指令字中少了一个地址码字段,所以缩短了指令字长;
- 不过需要额外增加 存储操作数 或 隐含地址 的硬件;
⑥ 立即寻址
立即寻址,操作数 本身就在 指令中;
特点:
- 取指令阶段访存一次,指令执行阶段无需访存;
- A的位数限制了 立即数 的大小;
用#表明是立即寻址的方式!
假设有效部分是n位,机器字长是n+1位
反码表示数(真值)的范围:
表示的范围是一样的,因为真值最小是一样的,就是当n是整数时,真值负数最小时,原码是1111 1111,反码是1000 0000。
补码表示数(真值)的范围:
设位数一共为8位
原码表示范围为 -127-127,即1111 1111~0111 1111
反码表示范围为 -127-127,即1000 0000~0111 1111
补码表示范围为 -128-127,即1000 0000~0111 1111看上面的第一个图,就可以知道,二进制的所表示的还是那么多,但是因为十进制的-0和+0合并成为了一个,所以相当于下面一行的十进制负数部分向右移动一位,使得补码能够多表示一位-128,对应的最小负数的二进制数仍然是1000 0000。
后面小数那里也是一样的。还是设位数一共8位
原码表示范围为1.111 1111 ~0.111 1111,即-127/128到127/128
反码表示范围为1.000 0000~0.111 1111,即-127/128到127/128
补码表示范围为1.000 0000~0.111 1111,即-1到127/128
小结
2.3 数据寻址 (补充)
1. 堆栈寻址
栈:只可以从数据的一端插入或者删除
堆栈寻址可以使用:1、寄存器【硬堆栈】 2、在主存中画出一部分区域作为堆栈【软堆栈】
①硬堆栈
POP:弹出栈顶的元素
PUSH:把计算结果压回栈顶
POP和PUSH就是使用的堆栈寻址,这两个指令所储存的操作数所存放的位置是被隐含在SP当中
②软堆栈
执行期间:在软堆栈中,pop和push 都一定需要一次访存,但是对于硬堆栈 这些元素都存放在寄存器中因此pop和push都不需要访存;因此硬堆栈的速度更快但是成本更高
但是对指令的的取出是要访存的【不在执行期间】
2. 偏移寻址
在 4.2.1 指令寻址 的 跳跃寻址已经提前预示了,如果对 跳跃寻址 不熟悉,先回去复习;
① 基址寻址
基址寻址,设有 基址寄存器 (BR) ,即
EA = (BR) + A
; 也就说,有效地址 等于 BR中记录的 基地址 + 指令中的 形式地址 ;
BR可采用 隐式 和 显式 两种实现方式:
面向操作系统的
隐式BR:计算机内部 专门 设置一个BR,指令不必明确指出基址寄存器,只需由指令的 寻址特征 为反映出基质寻址即可;
显式BR:在一组通用寄存器里,指令中 明确指出 哪个寄存器用作基址寄存器,存放基地址;
高级语言写的代码 通过编译语言等一系列操作之后形成与之对等的机器指令和数据;指令和数据被无差别的存入到主存当中
这个是直接寻址
这个不能用直接寻址 只能用基址寻址
程序的浮动:只需要更改BR的其实位置就可以
特点:
- A 和 BR 拼接出来的新地址无疑更长了,扩大了操作数的寻址范围;
- 有利于多道程序;
- 在程序执行过程中,BR的内容不变(基地址不变),形式地址可变(偏移量);
- 在显式中,虽然可以选择哪个哪个寄存器作为BR,但其内容由操作系统或管理程序确定;
普通的程序员不可以操纵 BR里的值 ;写的应用程序放在内存的什么位置应该是由操作系统决定的
普通程序员可以通过汇编语言来直接操纵某一个寄存器里的内容【读/写】,但如果通用寄存器被指定为基址寄存器时,那么这里面的值就不可以被随意的修改
② 变址寻址
变址寻址,和基址寻址类型,即
EA = (IX) + A
; 变址和基址的思想相同,只不过 变址寄存器 IX 的内容由用户决定;
可以将A看作时基地址 IX为偏移量
这个土办法:每一次循环都需要对应一个指令,这就会导致变成很不方便很不灵活--所以引入变址寻址 实现循环操作!
5不跳转的时候就接着往下执行!变址寻址的IX是用户可以通过指令修改的【1号指令】
基址和变址的复合寻址
把每一种寻址方式看作是一个函数 那么复合的寻址理论上就是一个复合函数!
③ 相对寻址
由程序计数器PC所指的地址作为 基地址,即
EA = (PC) + A
;
实质是对PC进行赋值,让PC指回M 优点:无论怎末移动 循环指令都会跳转到原来的位置!
小结
硬件如何实现数的比较 【前面的坑】
a-b的结果记录在4个标志位里,然后根据这几个标志位进行判断
je == jump when equal[等于的时候跳]
jg = jump where greater[更大的时候跳]
jmp 无条件转移指令
3 程序的机器级代码表示
2022 年大纲新增考点
3.1 高级语言与机器级代码之间的对应
机器级代码:机器语言、汇编语言
1. 考试要求
汇编语言指令和机器指令是一一对应的 一个汇编语言对应一个机器指令!
2. x86汇编语言指令基础【地址码】
内存地址通常使用16进制来表示
x86架构CPU,常用的寄存器:
- 通用寄存器:EAX、EBX、ECX、EDX;
- 变址寄存器:ESI、EDI;
- 堆栈基地址:EBP;
- 堆栈顶指针:ESP;
E开头的就是 32bit的寄存器!
mov eax,ebx:直接指明寄存器的名字,对这两个寄存器里的值进行操作:相当于寄存器寻址!
mov eax,dword ptr[af996h]:地址是16进制 用双字节寻址 32bit,后面是直接寻址+基址寻址 前面还是寄存器寻址
通用寄存器使用比较灵活,但对于变址寄存器、堆栈基指针、堆栈顶指针就不能修改;
把通用寄存器的名称从EAX 写成AX
3.更多例子
1、相当于寄存器间接寻址 []中括号 主存地址!
小结
3.2 常用汇编指令介绍
1. 常用的x86指令【操作码】
① 常见的算术运算指令
d、s是操作数 有可能来自什么地方 就是上节课里的地方
d不能是常量 这样就没有地址了没有办法放回了! 所以d只能是 来自于 寄存器或者是主存
其它操作较为简单明了,这里重点介绍除法操作:
div s
,无符号数除法idiv s
,有符号数除法 除法指令里只有一个操作数s(除数),而被除数已经被提前放进 edx、eax 里面【被除数是隐含寻址】;
edx:eax
的含义: 在进行除法之前,会对被除数进行位扩展[除数是32bit 那么被除数就要扩展成64bit],而扩展之后的被除数需要两个寄存器存放;
注意实现:
- 左边目的操作数不能是常量;
- x86语言里 不允许两个操作数同时来自于主存;【为了保证每一条指令不要访问太多次主存,访问主存的次数越多 运行时间就越长】
② 常见的逻辑运算指令
2. 汇编指令格式[AT&T/Intel]
mov 是ebx复制一份到eax里
mov没指明长度,默认就是32bit
3.3 选择语句的机器级表示[if-else]
IP寄存器其实就是程序计数器!指令通常是顺序进行的
1.转移类指令[程序执行流的跳转]
地址参数的三个来源 来自一个地址[](用中括号括起来)
jump到锚定的那个位置 : 最灵活的就是标号锚定!
jump相同与go to指令
2.常见的条件转移指令
3.举例
4.扩展:cmp指令的底层原理
PSW!每进行一次运算就会把上次的信息覆盖掉
3.4 循环语句的机器级表示
①条件转移指令
②loop指令
注意!loop 指令 必须配套 ecx,不能使用eax、ebx;
ecx特殊功能:作为循环的计数器!不可以用别的 自减的操作只能作用在ecx上!
i是一个计数的功能!
ZF=0 说明最近的一次运算的结果不是0
4 函数调用的机器级表示
4.1 Call和ret指令
1.函数调用的过程
当Qreturn时 会删除Q的栈帧
2.x86汇编语言的函数调用
在CPU内部有一个寄存器:PC[程序计数器],PC的作用就是指向接下来要运行的那个指令;当程序的执行流发生跳转的时候 本质上就是把PC寄存器指向另一个位置!call和ret指令都会是执行流发生变化,所以都会影响PC寄存器的值
4.2 访问栈帧
1.基础知识
①函数调用栈在内存中的位置[为什倒]
②EBP、ESP
在一个CPU内部只有一个EBP和一个ESP
2.访问栈帧数据
①push、pop
指令为执行前的状态
执行所有指令之后:
push和pop只能对栈顶的位置进行读或写,这样的限制会使我们想要访问栈的其他位置的时候会很不方便 更灵活的方法 mov指令
②mov
小结
4.3 切换栈帧
esp和ebp标记了当前执行的函数栈帧的范围,当发生函数的调用的时候要修改ebp\esp 让他们指向新的函数的底部和顶部!
①函数调用时,切换栈帧
执行call:
执行push\mov两条指令:
执行add压入数据操作后:
②函数返回时,切换栈帧
执行第一条mov:
执行第二条pop:
ret指令:
小结:
例题:
4.4 传递参数和返回值
1.一个栈帧内可能包含哪些内容
如果当前函数不会调用下一个函数,那么该当前函数的栈帧的大小不需要是16字节对齐的
访问局部变量:[ebp-4][ebp-8]
调用参数:[ebp+4][ebp+8]
为了追求数据的对齐,每个栈帧的到校时16B的整数倍;当前的函数可以不是,但是一旦这个函数要调用其他函数 那么他的栈帧就必须凑到16B的整数倍!--所以空闲区域就产生了,相当于时用不完的零头!
函数调用时如何调用参数呢?在call指令发起函数调用之前,我们需要把调用的参数写入到栈帧顶部的区域!
2.汇编代码实战
在caller函数之前有一个p函数,偏函数调用了caller函数
caller执行前两个指令后:
执行sub指令后
再执行两天mov指令
执行两个mov指令
mov指令不支持两个操作数同时来自主存 所以只能从主存复制到寄存器 再从寄存器复制到主存
再执行两个mov指令
执行call指令后
执行push 和mov指令后
执行两条mov指令
执行add指令后
执行完leave指令
执行完ret指令
执行完mov指令后
再执行下一条mov指令:将函数的返回值存入寄存器里,如果有需要的话直接拿!
小结
假如在函数调用的过程中 有一些运算结果 存储到某些寄存器中 比如edx,ecx,eax ;当函数调用的时候被调用者也有可能使用到这几个寄存器,这样会造成之前的值被覆盖掉 ,导致数据的丢失。如何解决? 在发起函数调用之前,把我所需要的寄存器的值压栈保存,当函数调用返回之后,我再把我保存的值从栈恢复到寄存器里
5 CISC 和 RISC 【指令系统的两种设计方向】
CISC中实现矩阵的乘法只靠硬件直接实现是比较困难的,所以采用存储结构的思想:给定一串基本的指令,把它提前存储在某个地方。矩阵的乘法可以通过那5个基本电路来实现【这就是微程序的概念】
RISC所有的指令都是简单指令运行的时间差不多,这个特性可以很方便的实现“并行”、“流水线”
load:读某个主存单元到寄存器中;store:从某个寄存器里向内存写某个数据
CICS的主要特点:
1)指令系统复杂庞大,指令数目一般多达200~300条。2)指令长度不固定,指令格式种类多,寻址方式种类多。
3)可以访存的指令不受限制(RISC只有取数/存数指令访问存储器)
4)各种指令执行时间相差很大,大多数指令需多个时钟周期才能完成。
5)控制器大多数采用微程序控制。【效率更低】
6)难以用优化编译生成高效的目标代码程序【相当于C语言的库,你觉得不好用但是难以优化库函数】
RISC的主要特点:
1)选取使用频率较高的一些简单指令以及一些很有用但不复杂的指令,让复杂指令的功能由使用频率高的简单指令的组合来实现。2)指令长度固定,指令格式种类少,寻址方式种类少。
3)只有取数/存数指令访问存储器,其余指令的操作都在寄存器内完成。【所以寄存器多】
4)CPU中有多个通用寄存器(比CICS的多)
5)采用流水线技术(RISC一定采用流水线),大部分指令在一个时钟周期内完成。采用超标量超流水线技术,可使每条指令的平均时间小于一个时钟周期。
6)控制器采用组合逻辑控制,不用微程序控制。【效率更高】
7)采用优化的编译程序【全是自己写的 当然好优化】
RISC与CICS的比较
1.RISC比CICS更能提高计算机运算速度;RISC寄存器多,就可以减少访存次数,指令数和寻址方式少,因此指令译码较快。2.RISC比CISC更便于设计,可降低成本,提高可靠性。
3.RISC能有效支持高级语言程序。
4.CICS的指令系统比较丰富,有专用指令来完成特定的功能,因此处理特殊任务效率高。
eg:
CISC的乘法指令:直接把相乘的数取到某一个寄存器,然后紧接着完成乘法操作,不会过多的占用寄存器
RISC的乘法指令:无论加减乘除都需要用LOAD将数据读入某一个寄存器,然后再用一条乘法指令实现相乘
5 常见问题和易混淆知识点
1.简述各常见指令寻址方式的特点和适用情况
立即寻址操作数获取便捷,通常用于给寄存器赋初值;
直接寻址相对于立即寻址,缩短了指令长度;
间接寻址扩大了寻址范围,便于编制程序,易于完成子程序返回。寄存器寻址的指令字较短,指令执行速度较快;
寄存器间接寻址扩大了寻址范围;
基址寻址扩大了操作数寻址范围,适用于多道程序设计,常用于为程序或数据分配存储空间;
变址寻址主要用于处理数组问题,适合编制循环程序。相对寻址用于控制程序的执行顺序、转移等;
基址寻址和变址寻址的区别:两种方式有效地址的形成都是寄存器内容+偏移地址,但是在基址寻址中,程序员操作的是偏移地址,基址寄存器的内容由操作系统控制,在执行过程中是动态调整的;而在变址寻址中,程序员操作的是变址寄存器,偏移地址是固定不变的;
2. 一个操作数在内存可能占多个单元,怎样在指令中给出操作数的地址 ?
现代计算机都采用字节编址方式,即一个内存单元只能存放一字节的信息;
一个操作数 (如:char、int、float、double)可能是8位、16位、32位或64位等,因此可能占用1个、2个、4个或8个内存单元
也就是说,一个操作数可能有多个内存地址对应。有两种不同的地址指定方式:大端方式和小端方式;
- 大端方式:指令中给出的地址是操作数最高有效字节(MSB)所在的地址;
- 小端方式:指令中给出的地址是操作数最低有效字节(LSB)所在的地址。
3. 装入/存储( Load/Store)型指令有什么特点 ?
装入/存储型指令是用在规整型指令系统中的一种通用寄存器型指令风格;
这种指令风格在RISC指令系统中较为常见。为了规整指令格式,使指令具有相同的长度,规定只有Load/Store指令才能访问内存;
而运算指令不能直接访问内存,只能从寄存器取数进行运算,运算的结果也只能送到寄存器;
因为寄存器编号较短,而主存地址位数较长,通过某种方式可使运算指令和访存指令的长度一致;
这种装入/存储型风格的指令系统的最大特点是,指令格式规整,指令长度一致,一般为32位;
由于只有Load/Store 指令才能访问内存,程序中可能会包含许多装入指令和存储指令,与一般通用寄存器型指令风格相比,其程序长度会更长;
1) 什么是指令 ?什么是指令系统? 为什么要引入指令系统 ?
指令就是要计算机执行某种操作的命令;
一台计算机中所有机器指令的集合,称为这台计算机的指令系统;
引入指令系统后,避免了用户与二进制代码直接接触,使得用户编写程序更为方便。另外,指令系统是表征一台计算机性能的重要因素,它的格式与功能不仅直接影响到机器的硬件结构,而且也直接影响到系统软件,影响到机器的适用范围。
2)一般来说,指令分为哪些部分 ? 每部分有什么用处 ?
一条指令通常包括操作码字段和地址码字段两部分;
其中,操作码指出指令中该指令应该执行什么性质的操作和具有何种功能,它是识别指令、了解指令功能与区分操作数地址内容的组成和使用方法等的关键信息。地址码用于给出被操作的信息(指令或数据)的地址,包括参加运算的一个或多个操作数所在的地址、运算结果的保存地址、程序的转移地址、被调用子程序的入口地址等。
3) 对于一个指令系统来说,寻址方式多和少有什么影响 ?
寻址方式的多样化能让用户编程更为方便,但多重寻址方式会造成CPU结构的复杂化(详见下章),也不利于指令流水线的运行。而寻址方式太少虽然能够提高CPU的效率,但对于用户而言,少数几种寻址方式会使编程变得复杂,很难满足用户的需求。