1.1 在计算机系统结构中的定位、指令系统概念及分类
1.1.1 在计算机系统结构中的定位、指令系统概念及分类
汇编语言可以认为是一个指令集的助记符。
汇编语言,很大程度上,可以认为是机器语言的一种助记符,跟它一一对应(大致上可以这么说)。
1.2 课程内容与目标
1.2.1 课程内容与目标
1.3 指令集简介
1.3.1 指令集简介——CISC
变长指令集有个好处是什么呢?
就是对于码字的这个利用率会比较高。大家可以想象 就是说 如果你做个加法 加法本身呢是一个 这个 寄存器加某个立即数 那立即数你可能用个8位表示就够了 实际上用个4为表示就够了 但有的可能用16位32位表示 那如果你这指令字是固定长度的话呢 如果你需要8位表示就够 但给你提供了16位的这个码位 那剩下的8位就是空了 就没用 浪费了 那么如果你这个数比较大 需要32位才能够表示 你又给你留了固定的16位又不够用啊 那就要拆成两条指令才能把它表示出来 这就有点浪费 不太经济 变长指令就没这个问题 当然变长指令也有个不好的地方 就是 它译码 读取指令比较复杂 你不像说定长指令 一次32比特 挨着读就完了 你也不用去切分它 你变长指令的话呢 你不知道下一次要读多长 这个就比较复杂一点 就各有特色吧
目前呢实话说啊在现代的X86处理器的内部 虽然它对外这个指令集 因为它后向兼容嘛 还是CISC 叫做复杂指令集 它没有变过 只是在那里面做扩展 但它的内部呢 它的内核啊 它内部实现上 用的还是类RISC的内核 就是精简指令集内核 就是采用啊 就是一条复杂指令读进来之后呢 内部先做了一个简单的对应翻译 用micro op micro op呢就是微操作码啦 这个微操作码是类似于这种RISC的指令 就是内部做了一个翻译 那么这样去运行的 也就是说它是这个CISC 可以认为是CISC的一个表 一个皮 但内核儿呢 里头呢 实际上是以RISC 或者说类RISC为体 大概就这个样子 所以我们说它在走向融合 这一块儿呢 就是说也使得它的功耗呢就相对会增大 因为你多了一个步骤 所以在很多这种低功耗的场合呢不易占优势 但Intel在工艺上是有优势的 也可以两者 还是有比拼的余地的 那么对于许多领域而言呢 资源利用率会低一点 就根据 比方说我们举个例子 在高性能计算领域 很多X86指令就用不上 尤其科学计算之类的
1.3.2 指令集简介——RISC
如果今天 一定要去区分CISC RISC的话呢 实际上有一个比较本质上的一个区别 就是大家还都保留着 就是说 RISC指令只能通过Load Store来访问内存 而CISC不受此限 就是 就是一定要这个划分一刀的话 这是一个它们之间一个 本质的一个区别 本质的区别 那么对于这个RISC 这个经典代表这MIPS而言呢 它的Load Store指令 都是属于这种立即数类型 就是说它有一个操作数 是一个寄存器作为基础寄存器 再加上一个立即数 就是说你要访问的这个内存的地址 把它取进来导出去 就放在你寄存器里头就是可以了 就Load Store 然后MIPS有各种扩展的指令集
第四个呢就是有个蛮有意思的 就是说 大多数指令呢具有“条件执行”的模式 条件执行的模式我们在X86里面会具体讲到 就所谓就是这条指令本身是不是执行 是不是起作用取决于某个条件是不是满足 X86里面呢会具体讲这方面的内容 对ARM来说呢 基本上它的绝大部分的指令都具有这种模式 这是其他指令集所欠缺的 是它的一个大特点 这个就有利于什么 就是说它的代码会比较精简 比较精炼 这是它的一大特点
1.4 整数的机器表示
1.4.1 整数的机器表示(一)
(C语言中的:
与:&&
或:||
非:!
异或:???
(相同取 0 ,不同取 1 。)
)
那么这里面有个小小的问题是什么 就是 一个机器字之内啊 各个字节是怎么排列的 因为我们知道比如说你一个32位字 32位字呢它占4个字节 0000到0003 那么要注意了 那么你这个字的高位字节 那么算是排到0000呢还是排到0003呢 那么有两种排法啦 一个正过来一个倒过来 那确实历史上呢 也确实存在这两种的 这种字节的排列方式 到现在还是这样子 一个叫大端一个叫小端 那就像那所说 那么我们重点要讲的X86呢 就所谓小端排布 什么意思呢 就是说 低对低高对高 实际上看下面这个图就很清楚啦 看最底下这个啊 这个 就是如果一个数值是0X01234567 地址呢是0X100 那么这0X100 那么是这个32位字的低字节的 它的低地址 用它来标识嘛 那么这个低地址所对应的这个字节 它所表 它所存储的 是这个数的高字节还是低字节呢 刚才我说过了 原则就是低对低高对高 就是小端的话就是低对低高对高 所以就存出来就是67 45 23 01 就是X86是这么存的 实际上ARM也是这样子 其他的一些机型 应该叫做sparc sun sparc架构 当然现在sun公司不存在了 被Oracle收购啦 PowerPC 他们这个机型呢 就是采用这个大端 也就是说低位字节占据高地址 这个大家呢 就是 当然日常生活当中不太会遇到这个事儿 但是有一个要注意 就是Internet 这Internet上面的控制字节序是大端的 这个我想许多同学 我们不展开 很多人如果是写过这种 Internet编程 就是Socket编程的话 比较底层的这种编程的话 可能会碰到这样的问题 就是如果你要去 连接一个服务器的一个地址 你要指定对方服务器的ip和它的端口号 那这个时候你指定这个信息的时候啊 你注意要用大端数据表示 印象当中是这样子 当然系统提供了一些调用来做这种转换 所以 这个东西有的时候就是展开讲讲看 有点意思 就是说 如果两台 计算机之间进行通讯 数据传输的话 如果它们 在这个字节排列序上不一样的话 可能会造成问题 大家想想看 是会造成问题 咱们不展开说啦 但对我们来说呢 重点是讲X86 所以它是小端 小端的特点是什么呢 就是低对低高对高 当然你说这个问题呢 到底对性能会有多大的影响 其实没有 这基本上没有什么影响 就是历史上呢无非就是说存在这两种排法 那么大家做机器的时候一开头也没有商量 就你这么做我就这么做就完了 就这么个事儿
1.4.2 整数的机器表示(二)
带符号就复杂一点 它就是有正负 当然还有0 正负之分 那么它的表达形式呢 严格来说叫做补码 补码是怎么来法呢 看补码下面这个式子 就是这样子啊 就是说 补码本身呢 在机器层面看起来 在硬件层面看起来 还是一个0101这么一个串 这看起来它跟无符号数没什么区别 但是它的最高位有区别 表示它的符号位 注意 这个符号位呢 不是我们想象的那种 比如说 它的符号位仅仅表示正还是负 注意 它这个符号位是什么呢 要乘上一个它的相关的二进制的权重的 那什么意思呢 也就是说如果这个 符号位是0 那当然是0乘以什么都是0啦 那么它所表达的这个数肯定是非负的 那么 如果这符号位 是1的话 就变成 负1乘上2的W减1啦 然后再加上后续那些位数 那些计算方式跟无符号数是一样的 所以说这个时候要注意 就是说我们说它是符号位啊 它的符号位是带权重的 带它的二进制权重 那么这里面又给出了几个例子 就是说 假设啊 我们有个short int short int什么意思啊 就是一个signed的 带符号的一个16位的一个整型 比如x=12345 那么它的负数就是-12345 那么 当然这是属于带符号数 补码表示 补码表示在这个表上面 我们可以看到什么 x y它们两个绝对值一样 只是正负颠倒啦 那么我们可以看到 用补码表示的这些带符号数 它的正数和负数之间 有个什么样的 对应关系呢 咱们不解释啊 就是说直接说结论 咱们可以看到什么呢 就是 比如说x 00110000 00111001 就是上面表上第一行 我们可以看到 这个二进制串按位取反 取反完了之后呢 当然不要忘了末位在加个1 这是什么啊 正是它的这个什么啊 它的负数的补码表示 那翻过来讲呢 就是如果这个数是-12345 12345就是这个位串 这个字符串 这个位串 这个位串按位取反加1 就变成了什么啊 上面这个数 这个大家可以 底下稍微这个一眼就看得出来 这个蛮简单的 也就是说这个带符号数这个补码表示啊 有个非常有意思的一个属性就是什么啊 就是 你要知道一个补码 这个数如果是个负数 你要去得出它的补码 怎么去算呢 那你就把它的正数 正数就是按照原码001给它算出来 然后呢按位取反加1 就是它的这个 这个它的补码就获得啦 这是它的很好的一个属性 那么这里头就是我们说啊 无符号数的表示比较简单 因为就是原码的表示 就是个0101位串乘上每个位所在的 二进制的权重 带符号数呢就补码表示 稍微复杂一点点 就是它的最高位呢表示它的符号位 同时要注意它这个符号位呢 要乘上 不仅仅只表示符号 它带权重 乘上它的相对的二进制的权重 后续那些位串的计算就跟原码是一样的 就这么来就行了 那么这样子的话呢 补码 表示带符号数 那么这个补码表示的最高位是0还是1 如果它是0的话呢 那这个数就是0或者说是大于等于0 那么它的最高位数是1的话呢 那这个数就是个负数 而在无符号数里头呢 所有的一切都是大于等于0就没这个概念
(
负数的补码怎么计算?
把负数对应的正数的原码算出来,然后按位取反,最后加 1 。
例如,-12345对应的正数是12345,12345的原码是1100 1111 1100 0111,取反是0011 0000 0011 1000,加1是0011 0000 0011 1001。
)
1.5 无符号整数与带符号整数
1.5.1 无符号整数与带符号整数(一)
补码呢稍微复杂一点点 它里面当然有0啦 但是因为它上面有个符号位 可以表示正负 那这种情况下的话呢 补码四个数里头 如果这个位宽确定的话 它最小数呢是1全0 1全0这个值是多少呢 负的2 W减1 次方 这个值怎么算实际上很简单 你把它按位取反再加个1 就是了 就是它的绝对值就是它的 这个位串表示的按位取反加个1啦 实际上就是它自个儿啦 绝对值是它一样的 前面加个负号就可以啦
(
带符号数(补码)怎算出来的?
最小数是 1 加全 0 ,等于 -2^(w-1)
。
怎算出来的呢?
把它按位取反,再加 1 。
)
无符号数带符号数 如果它们位宽相等的话 它们的取值范围的对应关系是这个样子 这个图当然画得比较直观啦 就是说 你说这个带符号数 带符号数如果是大于等于0的话呢 那么它们转换成无符号数不变 还是这个value还是这个值 但是呢 如果你的带符号数是小于0的话 因为无符号数里面没有小于0这个东西 没有这个概念 它小于0这个值呢 怎么着 一下就翻过去 就变成大于0了 它就变成比较大的那些数了 大家看一下 它的负1呢 就变成啦 这个无符号数里面最大的那一个 对 就变成它最大的那一个
signed 跟 unsigned 一块儿比较,就是大家默认转换成 unsigned 。
(
示例二
sizeof 返回的数据类型是 unsigned 。
signed 跟 unsigned 一块儿比较,默认转换成 unsigned 。
)
要注意 因为你这个机器表示的数据位宽有限 比如说w等于32 那么如果你有一个向30 就是向高一位的一个进位 那么这个进位在结果里面就被剔掉啦 就不存在啦 所以说呢 两个u加v 无符号整数相加 实际上等于它加出来这个数值啊 取模的一个2的w次方 所以说这个时候就可能产生溢出啦 就是有两个你无符号数 无符号数都大于等于0嘛 相加 如果这数比较大的话 向上有个进位的话 那这个数值就不对啦 就产生数据的溢出
当然一般情况下呢就是说在C语言里头 它默认是不检测这种溢出的 当然大家写程序的时候呢 可能稍微要注意一点就是 你所采用的这个数据类型 在你这个运算范围之内 有没有可能溢出 所以有的时候就是说你要注意一点 如果觉得不靠谱的话 再把它的这个位宽选得大一点 你就不要用int 用long long之类的
1.5.2 无符号整数与带符号整数(二)
( $3
表示常量; shrl
: sh
就是 shift
的意思; r
表示往右移动; l
就是把eax
往右逻辑移动 3 位
)
算数右移什么意思呢 就是你整个位串往右移动的话呢 左端得补数 逻辑呢就是补0 逻辑右移就补0 算术右移补什么 补原来的符号位 原来这个数是正的你就补0 是负的话呢 那符号位是1那就补1 那么这个叫做sarl shrl就是逻辑右移 sarl我们回头会讲 就是叫做算术右移
(
带符号整数除以 2 的幂
采用算术右移,x<0时,会产生舍入错误。
例如,正数 7606.5 舍入为 7606 ,负数 -7606.5 舍入为 -7607 ,这里就产生偏差了,需要加上一个校正量,详解如下图或看视频。
)
1.6 浮点数的机器表示
1.6.1 浮点数的机器表示
1.6.2 浮点数的规格化与非规格化表示
(这一节看的不是很懂,建议重复看视频或者看书。)
1.6.3 浮点数表示的特性
(这一节看的不是很懂,建议重复看视频或者看书。)
1.6.4 如何给出浮点数表示
(
本段视频基本看懂了,但是建议再看一遍。
本段视频的内容略麻烦。
)
1.6.5 C语言中的浮点数
int转换成float呢 int还是32位宽 有效数据宽度 float有效数据只有23位 float的表示范围是远远大于int的表示范围 但是你有效位数不一样 注意这两个概念 一个是表示范围 数据大小的概念 另一个就是你表示精度的概念 就是int32位 它就是有效的 而float只有23位的有效的 表示它的精确值 就这个有效数据 那么这种情况下的话呢 不会溢出 但是可能会有rounding 会产生误差 实际上刚才我们通过那个 8位数的这个小宽度的小浮点数的例子 也看到了 转换进去转换出来之后 数字不一样了
参考文献:
1. 汇编语言程序设计 - 清华大学 - 学堂在线。