《汇编语言程序设计》学习笔记(1)一、基础知识
1.1 在计算机系统结构中的定位、指令系统概念及分类
1.1.1 在计算机系统结构中的定位、指令系统概念及分类

汇编语言可以认为是一个指令集的助记符。

汇编代码基本上可以被视为机器指令的一种辅助符号表示方式与之相对应大致而言可以说它们之间存在一一映射的关系








1.2 课程内容与目标
1.2.1 课程内容与目标








1.3 指令集简介
1.3.1 指令集简介——CISC

变长指令集有个好处是什么呢?
在编码效率方面表现较为突出。大家也可以想象,在编码过程中如果使用加法操作,则加法本身即为一个寄存器加上某个立即数的形式。其中这个立即数可以用8位表示足够满足需求,并进一步优化至4位即可满足基本要求;但某些情况下可能需要使用16位甚至32位来表示较大的数值范围。因此如果采用定长编码的方式(即每条指令固定占用32位),则能够有效避免剩余高位未被利用的情况(剩余的高位则未被利用 匪夷所思)。然而若将该指令字扩展为可变长度编码(即允许部分指令占用16位),那么当数值较大时就需要拆分成两条或多条指令才能完成编码过程(这种做法同样会导致代码长度增加)。不过这会带来一定的空间浪费问题 同时也不经济 变长指令虽然解决了空间浪费的问题 但在编译与执行过程中会增加一定的复杂性 因为无法像定长指令那样一次性完成全部操作(即无需提前分割代码块)。因此两种编码方式各有其独特的特点与适用场景




说实话,在现代X86处理器内部虽然对外使用的是CISC指令集体系(复杂指令集),但因为其后向兼容性并未发生根本性的改变。尽管如此,在内部进行了必要的扩展以适应更高的功能需求。然而在内核实现上它依然采用类RISC(精简指令集)架构这一特点使得其运行效率得到了一定程度的保障。具体来说当一条复杂指令被读入后处理器先进行了一次简单的对应翻译过程随后通过微操作码(micro op)序列将其分解为一系列更基础的操作步骤从而实现了对原始复杂指令的有效执行这一过程类似于RISC架构的特点因此在实际运行中这种设计使得处理器能够高效地处理各种类型的指令然而由于这种架构在功耗上会有所体现因为在内部多了一个中间环节导致整体功耗有所增加特别是在对低功耗环境有一定要求的应用场景下可能会显得力不从心不过Intel在工艺技术上仍然保持着一定的优势因此在这种架构与CISC架构的竞争中仍有一定的发挥空间对于许多领域而言资源利用率可能会因此受到影响例如在高性能计算领域很多X86特定的指令并不适用尤其是涉及科学计算等专业领域的工作负载
1.3.2 指令集简介——RISC




若今天有必要区分CISC与RISC时,则需注意二者的本质区别在于:前者仅可通过Load/Store指令访问内存空间,而后者则不受这一限制。
对于此类划分,则是一种根本性的区别:即针对该架构而言,RISC指令系以立即数型操作数为基础,并采用寄存器作为操作基址,因而其Load/Store指令均为立即数类型操作指令。
从这个角度来看,MIPS作为典型的RISC架构,其Load/Store指令集确实全部采用了立即数型操作数的形式,这使得相关操作更加直观简洁。



第四个内容相当有趣的是一个值得注意的部分 其中大部分指令遵循"条件执行"模式 在X86架构中我们将详细讲解这一机制 即使一条指令是否被执行取决于某个特定条件是否得到满足 在X86体系中我们将深入探讨这一概念 对于ARM架构来说 它们基本上具备与X86相似的特征 即绝大多数指令均遵循"条件执行"模式 这一特点是其他架构不具备的重要特征 它的存在使得代码编写更加高效和简洁 从而体现出其显著的优势




1.4 整数的机器表示
1.4.1 整数的机器表示(一)





(C语言中的:
与:&&
或:||
非:!
异或:???(相同取 0 ,不同取 1 。)
)



那么这里存在的问题是:机器字之内啊 各个字节的排列方式到底是怎样的?我们都知道:一个32位整数占据4个字节(即从内存地址空间中的第1号位置到第4号位置)。需要注意的是:那个高位的字节是位于第几号位置?其实有两种不同的处理方式:一种是从低位开始算起(小端),另一种是从高位开始算起(大端)。这两种方法都曾经被广泛使用,并且沿用至今依然是主流方法之一。例如,在 x86 处理器中就采用了小端排列法的特点:低地址对应的是低位存储单元。具体来说就是说,在内存中的某个特定位置存放了一个数值(比如 0X12345678),那么它的低两位数值 78 就会被存储在最低有效地址的位置上;而高两位数值 12 则会被存储在最高有效地址的位置上。这种做法的好处是可以直接反映数字本身的高低位关系。同样地,在 arm 和 sparc 等其他处理器中也采用了类似的小端方法来存储数据。需要注意的是,在现代互联网通信协议中,默认都是采用大端格式来表示数据长度信息的。这可能是一个让人容易混淆的地方:当我们在进行网络通信时,默认的数据传输都是以大端顺序进行编码与解码的


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 无符号整数与带符号整数(一)

补码的概念稍微有些复杂。然而,在这种表示体系中确实包含了零值。然而,在这种表示体系中存在一个符号位来区分正负数值。在这种情况下而言呢?补码系统中共有四个基本数值类型:当位宽固定时,补码中的最小数值对应于所有位均为1的情况;而最大数值则对应于所有位均为0的情况(除了符号位)。具体来说,在W位的补码系统中,最小数值为-2(W-1),其对应的十进制值即为负的最大范围数值;而最大正数值则为2(W-1)-1。计算这一最小数值的过程非常简单:将该二进制数全部取反后再加上一即可得到结果;这一过程实际上等于对该二进制数求其绝对值后再附加负号的操作。换句话说,在这种表示体系下每个非零数值都可以唯一地表示其绝对值和符号信息
(
带符号数(补码)怎算出来的?
最小数是 1 加全 0 ,等于 -2^(w-1)。
怎算出来的呢?
把它按位取反,再加 1 。
)


当两个具有相同位宽的无符号数与带符号数相同时 它们的取值范围对应关系清晰可见 而这个图显然具有直观性 你所说的是关于带符号数值的情况 当带符号数值大于等于零时 它们转换为无符号数值保持不变 即该value依然有效 当带符号数值小于零时 由于无符号数值中不存在负值这一概念 因此其对应的无符号数值将发生翻转 转换后数值变得比零大 并处于较大范围之内 大家来看一下 带有-1的无符号数值将被映射到该类型中所能取得的最大值


signed 跟 unsigned 一块儿比较,就是大家默认转换成 unsigned 。

(
示例二
sizeof 操作返回的数据类型是 unsigned 。
signed 和 unsigned 在进行对比时,默认将其转换为 unsigned 。
)

需要注意的是,在使用机器表示的数据时由于其数据位宽有限例如设w=32的情况下 如果将一个数值设定为30 这相当于向高一位进行了一次进位操作 这种进位操作在计算结果中会被截断而不复存在 因此 两个无符号整数u和v相加的实际过程等同于计算它们之和并对2^w取模的结果 当两者的和超过最大允许值时(即发生向上的一次进位) 就会导致数值溢出 这种情况发生在两个无符号整数均为非负值且其和超过最大允许值时 即使发生了向上的一次进位 也会导致计算结果不符合预期
通常情况下,在C语言中,默认不会检测溢出。当编写程序时需要注意的是:所使用的数据类型是否在你的运算范围内可能导致溢出?因此,在某些情况下需要注意选择合适的位宽。如果担心不够安全,则可以考虑使用更宽的数据类型,比如long long等。
1.5.2 无符号整数与带符号整数(二)



($3)代表常数值;
shrl: 其中,
sh意为将数据左移;
r指示数据向右移动;
l操作涉及将eax字段向右逻辑移位三位。
算术右移的具体含义是什么呢?也就是说整个二进制位串向右移动时左边需要填充相应的数值。对于逻辑运算来说这种操作通常会用零来填充被截断的部分而至于算术运算则有所不同它会在执行时如何处理数值?对于正数来说在进行算术右移到的时候左边会填充零而对于负数来说则会在左边保留原有的符号位即如果是-1(二进制表示为全1)那么在进行算术右移到的时候左边也会填充1以保持其负性这一操作通常被称为sarl(arithmetic shift right)而对应的逻辑操作则称为shrl(logical shift right)。其中sarl我们会在后续章节中详细介绍它的具体实现方式


(
带符号整数除以 2 的幂
采用算术右移,x<0时,会产生舍入错误。
举例来说,在数值处理中,默认情况下会对正值进行四舍五入操作(如将 7,899 四舍五入到最近的百位得到 8,899),而对于负值则会采用相反的方式(如将-8,899 四舍五入到最近的百位得到-8,900)。这就会导致数值之间产生一定的误差差额。为了弥补这种差异带来的影响,在后续计算中必须再加上一个修正值(correction factor)。详细解释可以通过下图或观看视频来了解。
举例来说,在数值处理中,默认情况下会对正值进行四舍五入操作(如将 7,899 四舍五入到最近的百位得到 8,899),而对于负值则会采用相反的方式(如将-8,899 四舍五入到最近的百位得到-8,900)。这就会导致数值之间产生一定的误差差额。为了弥补这种差异带来的影响,在后续计算中必须再加上一个修正值(correction factor)。详细解释可以通过下图或观看视频来了解。





1.6 浮点数的机器表示
1.6.1 浮点数的机器表示





1.6.2 浮点数的规格化与非规格化表示
(这一节看的不是很懂,建议重复看视频或者看书。)





1.6.3 浮点数表示的特性
(这一节看的不是很懂,建议重复看视频或者看书。)








1.6.4 如何给出浮点数表示
(
本段视频基本看懂了,但是建议再看一遍。
本段视频的内容略麻烦。
)







1.6.5 C语言中的浮点数

int转为float呢?虽然int仍然是32位宽的,但其有效数值宽度与float相比显得较小。具体来说,float的有效数值仅有23位,在表示精度方面明显低于int的数据大小概念。然而它们的有效位数不同。值得注意的是,在这种情况下不会溢出数值范围的变化会导致结果值存在微小差异。实际上,在之前我们通过一个使用8位小宽度的小浮点数的例子也观察到过类似的状况:将数值转换进制后又还原回原格式时会得到不同的数字结果。

汇编语言程序设计(清华大学学堂在线教育平台提供的课程资源)
