Arithmetic and Logic Instructions⚓︎
约 8370 个字 206 行代码 预计阅读时间 44 分钟
Addition, Subtraction and Comparison⚓︎
任何微处理器中的算术指令主要包括加法、减法和比较。
Addition⚓︎
- 加法(
ADD)在微处理器中以多种形式出现 -
唯二不允许的加法类型是内存到内存,以及段寄存器
- 段寄存器只能移动、压栈或弹出
-
当算术和逻辑指令执行时,标志寄存器的内容会改变
- 中断、陷阱和其他标志位不会因此改变
- 发生改变的是符号、零、进位、辅助进位、奇偶校验和溢出标志位
-
立即数加法:涉及到常量和已知数据的加法
-
内存 - 寄存器加法
- 将参与加法的内存数据移动到
AL(或别的)寄存器中 -
示例:将存储在数据段偏移位置的
NUMB的两个连续字节的数据相加,结果存储在AL寄存器中
- 将参与加法的内存数据移动到
-
数组加法
- 内存数组是连续的数据列表
-
假设一个数据数组(
ARRAY)包含 10 字节,从元素 0 编号到元素 9;下面展示了如何将数组元素 3、5 和 7 的内容相加: -
假设一个数据数组包含数字的字,用于在寄存器
AX中形成 16 位的和;下面的指令序列展示了用比例变址寻址添加名为ARRAY的内存区域中的元素 3、5 和 7ARRAY DW 0, 1, 3, 2, ... ... MOV EBX, OFFSET ARRAY ; address ARRAY MOV ECX, 3 ; address element 3 MOV AX, [EBX + 2 * ECX] ; get element 3 MOV ECX, 5 ; address element 5 ADD AX, [EBX + 2 * ECX] ; add element 5 MOV ECX, 7 ; address element 7 ADD AX, [EBX + 2 * ECX] ; add element 7EBX加载了ARRAY的地址ECX保存了数组元素编号- 比例因子用于将
ECX寄存器的内容乘以 2 以定位数据字
-
递增(increment) 指令(
INC)将 1 加到任何寄存器或内存位置(段寄存器除外)-
保持
CF(进位)标志的状态不变 -
使用间接内存递增时,数据的大小必须通过使用
BYTE PTR,WORD PTR或DWORD PTR指令来描述 -
汇编器无法确定
INC [DI]是字节大小、字大小还是双字大小的递增汇编语言 操作 INC BLBL = BL + 1INC SPSP = SP + 1INC EAXEAX = EAX + 1INC BYTE PTR[BX]对 BX所寻址的数据段内存位置的字节内容加 1INC WORD PTR[SI]对 SI所寻址的数据段内存位置的字内容加 1INC DWORD PTR[ECX]对 ECX所寻址的数据段内存位置的双字内容加 1INC DATA1对数据段内存位置 DATA1的内容加 1INC RCX对 RCX加 1(64 位模式)
-
-
ADC(带进位加法 (add-with-carry))将进位标志(C)中的位加到操作数数据上- 主要出现在 80386 到 Core2 中添加宽于 16 或 32 位数字的软件中
- 和
ADD类似,ADC在加法后会影响标志位 -
下图展示了该指令的操作细节:
例子
使用
ADC计算两个双字长的长整数之和。; [int1][0:7] = [int1][O:7] + [int2][0:7] .data int1 DD 1,0,0,0,0,0,0,1 int2 DD 1,0,0,0,0,0,0,1 .code MOV EDI, OFFSET int1 MOV ESI, OFFSET int2 MOV ECX, 8 ; ECX = 8 CLC ; start out with carry flag cleared loop: MOV EAX, [ESI] ADC [EDI], EAX ; with carry from previous loop pass LEA ESI, [ESI+4] ; point to next source LEA EDI, [EDI+4] ; point to next destination DEC ECX ; adjust loop count JNZ loop ; if ECX > 0 then repeat -
ADC的变体:ADCX和ADOX-
ADD和ADC指令可用于加速大整数算数运算,通过形如右侧的代码序列实现- 问题:这些指令创建了一个依赖链(dependency chain),因此处理器无法并行执行算术运算
-
为了解决这个问题,Intel 增加了第二条进位链,使得两个独立的进位链可同时发生;它们对应
ADC的两个变体ADCX和ADOX - 这两个变体不会相互影响,因为它们有单独的进位标志
ADCX使用进位标志,并保留其他标志不变ADOX使用溢出标志,并保留其他标志不变
-
ADCX、ADOX和MULX指令为大型整数乘法带来了很大便利- 而大型整数算术在密码学(例如 RSA 公钥算法)和高性能计算中有许多应用场景
-
-
XADD(交换和加(exchange and add))- 首次出现在 80486 中,并一直延续到 Core2
-
语法:
XADD des, src,对应操作如下:- 交换
des操作数与src操作数 - 将两个值的和加载到
des操作数中:des = src + des
- 交换
-
是少数改变源操作数的指令之一
-
例子:
-
目标操作数可以是寄存器或内存位置,而源操作数只能是寄存器
- 对于多处理器系统,
XADD可以与LOCK前缀在多处理器系统中结合使用,以允许多个处理器执行一个DO循环 int atomic_xadd(atomic_t *v, int inc)XADD将给定的增量inc加到*v上,并原子性地返回*v的先前值XADD在原子值*v上执行原子交换和加法操作- 当多个 CPU 在线时,
XADD会被锁住
XADD可以实现共享计数器和各种数据结构-
XADD可用于乐观锁(optimistic locking),主要适用于高并发系统中例子
以下指令使用乐观锁来安全地通过多个线程更新共享版本。
.data version DD 0 ; shared version number initialized to 0 .code MOV ECX, version ; load the current value of version ...... ; working optimistically MOV EAX, 1 ; EAX = 1 XADD version, EAX ; version ⇔ EAX, version = version+1 CMP EAX, ECX ; check if the value was modified by another thread JNE retry ; if version was updated then rollback ...... retry: ...... ; handle the conflict
Subtraction⚓︎
-
指令集中有许多形式的减法(
SUB)指令的- 使用任何寻址模式,带有 8 位、16 位或 32 位数据
-
寄存器加法:每次执行减法运算时,微处理器修改标志寄存器的内容
- 立即数加法:允许常量数据作为减法中的立即操作数
-
DEC(递减(decrement)) :从任何寄存器或内存位置减去 1- 影响除
CF外的所有标志位 - 像
CMP这样的指令会将所有标志位更新为执行结果,但#!asm INC和DEC会写入除CF之外的标志位 -
因此,如果
JCC直接使用来自INC/DEC的标志位,JCC可能会因为意外指令而产生错误依赖,例如: -
所以编译器通常不会生成用于循环计数更新,或重用
JCC上产生的INC/DEC的标志位
- 影响除
-
带借位的减法(subtract-with-borrow) 指令(
SBB)- 功能与普通减法相同,但包含借位的进位标志(
C)也要从差值中减去 - 常用于(在 80386 - Core 2)执行在超过 16 位或 32 位的数字上的减法
- 宽减法需要借位来传播,就像宽加法传播进位一样
-
下面展示该指令的运算细节:
- 功能与普通减法相同,但包含借位的进位标志(
Comparison⚓︎
比较(comparison) 指令(CMP)是一种只改变标志位,而不会改变目标操作数的减法,用于检查寄存器或内存位置的内容与另一个值是否相等。后面通常会跟一个条件跳转指令,用于测试标志位的条件。
| 汇编语言 | 操作 |
|---|---|
CMP CL, BL |
CL - BL |
CMP AX, SP |
AX - SP |
CMP EBP, ESI |
EBP - ESI |
CMP RDI, RSI |
RDI - RSI(64 位模式) |
CMP AX, 2000H |
AX - 2000H |
-
比较和交换指令(
CMPXCHG)比较目的操作数与累加器(accumulator)(隐式操作数 (implicit operand)) ,-
例子:
CMPXCHG des, src (AL/AX/EAX)- 如果
des == accu,则des = src,ZF = 1 - 如果
des != accu,则accu = des,ZF = 0 EFLAGS中的ZF位相应地被赋值
- 如果
-
仅存在于 80486 - Core2 指令集
- 指令功能与 8 位、16 位或 32 位数据相关
-
ZF标志在目标操作数和寄存器AL、AX或EAX中的值相等时被设置,否则被清除-
案例 1:
- 执行前:
(CX)=00FFH, (DX)=00EFH, (AX)=00FFH - 执行后:
(CX)=00EFH, (DX)=00EFH, (AX)=00FFH, ZF=1
- 执行前:
-
案例 2:
- 执行前:
(CX)=00FFH, (DX)=00EFH, (AX)=00EEH - 执行后:
(CX)=00FFH, (DX)=00EFH, (AX)=00FFH, ZF=0
- 执行前:
-
-
int atomic_cmpxchg(atomic_t *v, int new, int old)- 此函数使用给定的旧值和新值,在原子值
v上执行原子比较交换操作,返回操作前原子变量v的旧值 - 它在操作周围提供显式的内存屏障
- 例子
: CMPXCHG CX, DX (AX)(三个寄存器分别表示原子值、新值和旧值)- 若
CX == AX,DX被拷贝到CX中 - 若
CX != AX,CX被拷贝到AX中 AX保留执行前CX的值
- 若
- 此函数使用给定的旧值和新值,在原子值
-
-
CMPXCHG8B指令:比较并交换八个字节-
语法:
CMPXCHG8B [mem64-operand]ECX:EBX:64 位新值(隐式操作数)EDX:EAX:64 位旧值(隐式操作数)- 如果操作数 ==
EDX:EAX,则操作数 =ECX:EBX,ZF = 1,否则EDX:EAX= 操作数,ZF = 0–Z标志位表示比较后的值相等
-
CMPXCHG8B通常在多处理器环境中与LOCK前缀一起使用,以确保原子操作
-
-
CMPXCHG16B:将RDX:RAX中的 128 位值与内存中位于目的操作数的 128 位数字进行比较- 如果值相等,则将
RCX:RBX中的 128 位值存储到目的操作数中,否则目的操作数中的值将加载到RDX:RAX中 CMPXCHG16B要求目的(内存)操作数应为 16 字节对齐
- 如果值相等,则将
Multiplication and Division⚓︎
Multiplication⚓︎
- 早期的 8 位微处理器不能使用乘法或除法程序,只能通过一系列移位和加减运算来实现
- 后来,制造商们意识到了这种不足,于是他们将乘法和除法纳入了最新的微处理器指令集中
-
Pentium – Core2 包含特殊电路,可以在最少的时钟周期内完成乘法
- 而早期的处理器执行乘法需要超过 40 个时钟周期
-
乘法在字节、字或双字上执行
- 有无符号整数(
MUL)和符号整数(IMUL)两个版本 - 对于
MUL,乘数始终在AL/AX/EAX寄存器中作为隐式操作数,例如MUL CL ; AX = AL*CL
- 有无符号整数(
-
积始终是双宽度积(double-width product)
- 两个 8 位数字相乘生成一个 16 位积;两个 16 位数字生成一个 32 位积;两个 32 位数字生成一个 64 位积
- 在 Pentium 4 的 64 位模式下,两个 64 位数字相乘生成一个 128 位积
-
8 位乘法
- 使用 8 位乘法时,乘数始终在
AL寄存器中,符号 / 无符号整数- 乘数可以是任何 8 位寄存器或内存位置
- 使用内存操作数时需要指令来指示操作数大小,例如
MUL WORD PTR [BX]
- 没有立即数乘法的形式(形如
MUL 12H) ,除非是IMUL乘法的两个或三个操作数形式 - 乘法完成后,积放在
AX上 -
下面列举一些 8 位乘法指令:
汇编语言 操作 MUL CLAL乘以CL;无符号积在AX中IMUL DHAL乘以DH;符号积在AX中IMUL BYTE PTR[BX]AL乘以BX所寻址的数据段内存位置的字节内容;符号积在AX中MUL TEMPAL乘以内存位置TEMP的字节内容;无符号积在AX中
- 使用 8 位乘法时,乘数始终在
-
16 位乘法
- 此时乘数放在
AX中 - 32 位积放在
DX-AX中DX包含积的高 16 位AX包含积的低 16 位
- 与 8 位乘法一样,乘数的选取取决于程序员
- 此时乘数放在
-
32 位乘法
- 在 80386 及以上版本中可以实现 32 位乘法,因为这些微处理器包含 32 位寄存器
EAX寄存器的内容与指令指定的操作数相乘- 64 位积位于
EDX-EAX中,其中EAX包含积的低 32 位
-
64 位乘法
- 在 Pentium 4 中,64 位乘法的结果以 128 位积的形式出现在
RDX:RAX寄存器对中 - 尽管这种大小的乘法相对较少见,但 Pentium 4 和 Core 2 可以同时对符号和无符号数进行此操作
- 在 Pentium 4 中,64 位乘法的结果以 128 位积的形式出现在
-
IMUL指令有三种形式:- 单操作数形式:这种形式与
MUL指令使用的相同 -
双操作数形式:目的操作数是一个寄存器,源操作数是一个立即值、一个寄存器或一个内存位置,中间乘积(输入操作数大小的两倍)被截断并存储在目的操作数位置
-
三操作数形式:第一个源操作数乘以第二个源操作数,中间乘积被截断并存储在目的操作数
- 单操作数形式:这种形式与
-
一种特殊的立即数乘法
- 在双操作数或三操作数形式的
IMUL指令中,源操作数可以是一个立即值 - 当使用立即值作为操作数时,它会被符号扩展到目标操作数格式的长度
- 比如
IMUL CX, DX, 12H指令将12H乘以DX,并将一个 16 位符号积留在CX中 - 注意,双操作数形式会被汇编为三操作数以支持立即数乘法,例如,
IMUL BX, 16H->IMUL BX, BX, 16H
- 在双操作数或三操作数形式的
-
MUL和IMUL的差别:前者用零扩展填充高位,而后者用符号扩展填充高位 -
受
MUL影响的标志-
当积完全位于积的下寄存器内时,
MUL指令清除OF和CF标志,否则OF和CF标志被设置 -
CF和OF标志表示积的上半部分是否包含有效数字
-
-
受
IMUL影响的标志-
当中间乘积的符号整数值与符号扩展操作数大小的截断乘积不同时,
CF和OF标志被设置,否则CF和OF标志被清除 -
使用两个和三个操作数形式时,由于截断,应检查
CF或OF标志以确保没有重要的位丢失
-
思考
为什么 MUL 只有单操作数形式,而 IMUL 有扩展的双操作数和三操作数形式?
- 随着编程语言和编译器的开发,Intel 发现符号整数(如 C 中的
int)的使用频率远高于无符号整数(例如,表达式x = y * 10或x = y * z) - 因此,Intel 引入了
IMUL的双操作数和三操作数形式,以简化在汇编级别实现高级语言乘法语义的实现
为什么 IMUL 指令的两位操作数和三位操作数形式使用截断乘法而不是扩展乘法?
– 单操作数的 MUL 和 IMUL 最初是为高精度 / 大数字算术(例如加密、哈希计算)等场景设计的,这些场景更依赖于完整的 2N 位结果,因此它们使用扩展乘法
– 在编程语言中,对于像 c = a * b (其中 a、b 和 c 都是相同的 int 类型)这样的常见表达式,程序员关注的是结果的低 N 位,因此 IMUL 的双操作数和三操作数形式使用截断乘法
Division⚓︎
- 除法发生在 8 位、16 位和 32 位数字上,具体取决于微处理器
- 有无符号整数(
DIV)或有符号整数(IDIV)版本 - 被除数始终是双倍宽度的,除以操作数
- 没有任何微处理器有立即数除法指令可用
- 在 64 位模式下的 Pentium 4 和 Core 2 中,可以实现 128 位数字除以 64 位数字
-
一个除法操作可能导致两种类型的错误:
- 尝试除以零
- 除法溢出(发生在一个大数除以一个小数时)
-
在任何情况下,如果发生除法错误,微处理器都会生成一个中断,在屏幕上显示错误信息
-
8 位除法
- 使用
AX存储被任何 8 位寄存器或内存位置的内容除以的被除数(dividend),即用r/m8除以AX -
结果存储在
AL:= 商(quotient),AH:= 余数(remainder)
- 商或正或负,而余数始终取被除数的符号;这种舍入模式称为向零舍入(round-to-zero)
-
示例:
IDIV BL- 对于
AX=10H(+16) 和BL=0FDH(-3)- 结果:商为 -5 (
AL),余数为 1 (AH)
- 结果:商为 -5 (
- 对于
AX=0FFF0H(-16) 和BL=03H(+3)- 结果:商为 -5 (
AL),余数为 -1 (AH)
- 结果:商为 -5 (
- 对于
-
数字通常在 8 位除法中为 8 位宽。 – 被除数必须转换为
AX中的 16 位宽数字;对于符号和无符号数字,实现方式不同 -
以下示例说明如何将内存位置
NUMB的无符号字节内容除以内存位置NUMB1的无符号内容(即,NUMB/NUMB1)MOV AL, NUMB ; get NUMB MOV AH, 0 ; zero-extend DIV NUMB1 ; divide by NUMB1 MOV ANSQ, AL ; save quotient MOV ANSR, AH ; save remainder- 注意:位置
NUMB的内容被零扩展,以形成一个 16 位无符号数,作为被除数
- 注意:位置
- 使用
-
16 位除法
- 此时以
DX-AX作为被除数(32 位) - 在 80386 以上的处理器中,使用
MOVZX指令对数字进行零扩展
- 此时以
-
32 位除法
- 80386 - Pertium 4 可在无符号或符号数上执行 32 位除法
EDX-EAX中的 64 位内容除以指令指定的操作数- 32 位的商和余数分别在
EAX和EDX中
-
64 位除法
- 处于 64 位模式的 Pertium 4 可在无符号或符号数上执行 32 位除法
- 使用
RDX-RAX来保存被除数 - 商和余数分别在
RAX和RDX中
-
3 种类型的符号扩展:
-
CBW/CWDE/CDQE:将AL/AX/EAX中的符号字节 / 字 / 双字转换为符号字 / 双字 / 在RAX中的四字指令 Op/En 64 位模式 兼容 / 传统模式 描述 CBWZO有效 有效 AX := AL的符号扩展CWDEZO有效 有效 EAX := AX的符号扩展CDQEZO有效 不适用 RAX := EAX的符号扩展 -
CWD/CDQ/CQO:将RAX的符号位拷贝到RDX寄存器的所有位上-
以下例子展示了通过
CWD指令对 2 个 16 位符号数的除法
-
-
MOVSX/MOVSXD:将源操作数(寄存器 / 内存位置)的内容通过符号扩展拷贝到目标操作数(寄存器)MOVSX reg16, reg/mem8 MOVSX reg32, reg/mem16 MOVSX reg32, reg/mem8 MOVSX reg64, reg/mem16 MOVSX reg64, reg/mem8 MOVSXD reg64, reg/mem32-
MOVZX:将源操作数(寄存器 / 内存位置)的内容通过零扩展拷贝到目标操作数(寄存器)
-
-
-
余数
- 在除法后,余数有以下几种处理方式:
- 截断求商(dropped to truncate the quotient):比如 13 / 2 = 6
- 舍入求商(round the quotient):如果除法是无符号的,舍入需要将余数与除数的一半进行比较,以决定是否将商数向上取整(比如 13 / 2 = 7)
- 分数余数(fractional remainder):比如 13 / 2 = 6.5
- 在除法后,余数有以下几种处理方式:
例子
下面展示了一个将 AX 除以 BL 的程序,并舍入无符号结果(即,AX / BL)
0000 F6 F3 DIV BL ; divide
0002 02 E4 ADD AH, AH ; double remainder
0004 ЗА ЕЗ CMP AH, BL ; test for rounding
0006 72 02 JB NEXT ; if OK
0008 FE CO INC AL ; round
000A NEXT:
- 在比较之前将余数加倍,以决定是否舍入求商
INC指令在比较后舍入AL的内容
下面展示了如何将 13 除以 2。
0000 B8 000D MOV AX, 13 ; load 13
0003 B3 02 MOV BL, 2 ; load 2
0005 F6 F3 DIV BL ; 13/2
0007 A2 0003 R MOV ANSQ, AL ; save quotient
000A BO 00 MOV AL, O ; clear AL
000C F6 F3 DIV BL ; generate remainder
000E A2 0004 R MOV ANSR, AL ; save remainder
- 8 位商被保存在内存位置
ANSQ中,然后 AL 被清零 - 接下来,
AX的内容再次除以 2,以生成一个分数余数 - 在第二次除法后,
AL寄存器的值为80H - 如果将二进制点(基数)放置在
AL的最左边位之前,则AL中的小数余数为 0.510 或 0.100000002 - 余数保存在内存位置
ANSR中
BCD and ASCII Arithmetic⚓︎
微处理器同时支持 BCD(二进制编码的十进制 (binary-coded decimal))和 ASCII 数据的算术操作。
- 但这些指令在 64 位模式下是无效的,强行使用会抛出无效操作码异常(invalid-opcode exception)。
BCD Arithmetic⚓︎
- BCD 运算发生在如小型终端(例如,收银机)和其他很少需要复杂算术的系统中
-
两种使用 BCD 数据进行运算的算术指令:
DAA(加法后十进制调整 (decimal just after addition))指令紧随 BCD 加法之后DAS(减法后十进制调整 (decimal just after subtraction))紧随 BCD 减法之后- 两者都纠正加法或减法的结果,使其成为 BCD 数,并且这些指令使用寄存器
AX作为源和目标
-
DAA指令:- 调整两个打包(packed) BCD 值的总和,以创建一个打包 BCD 结果
- 仅在跟随
ADD或ADC指令后面时才有用,这些指令将两个 2 位数字的打包 BCD 值相加(以二进制方式相加) ,并将字节结果存储在AL寄存器中 - 然后该指令调整
AL寄存器的内容,使其包含正确的 2 位数字打包 BCD 结果 - 如果检测到十进制进位,则
CF和AF(在加法后保存进位(半进位) )标志会相应设置
例子
0000 BA 1234 MOV DX, 1234H ; load 1234 BCD 0003 BB 3099 MOV BX, 3099Н ; load 3099 BCD 0006 8A C3 MOV AL, BL ; sum BL and DL 0008 02 C2 ADD AL, DL ; AL= CDH 000A 27 DAA ; AL = 33H, CF=1 000B 8A C8 MOV CL, AL ; answer to CL 000D 9A C7 MOV AL, BH ; sum BH, DH and carry 000F 12 C6 ADC AL, DH ; AL = 43H 0011 27 DAA 0012 8A E8 MOV CH, AL ; answer to CH ; CX= 4333H -
DAS指令:除了做减法运算外,和DAA指令基本一致例子
0000 BA 1234 MOV DX, 1234H ; load 1234 BCD 0003 BB 3099 MOV BX, 3099Н ; load 3099 BCD 0006 8A C3 MOV AL, BL ; subtract DL from BL 0008 2A C2 SUB AL, DL 000A 2F DAS 000B 8A C8 MOV CL, AL ; answer to CL 000D 9A C7 MOV AL, BH ; subtract DH 000F 1A C6 SBB AL, DH 0011 2F DAS 0012 8A E8 MOV CH, AL ; answer to CH
ASCII Arithmetic⚓︎
- ASCII 算术指令与编码数字一起工作,值
30H到39H代表 0 到 9 -
有以下四种指令:
AAA(ASCII 加法后调整)AAS(ASCII 减法后调整)AAM(ASCII 乘法后调整)AAD(ASCII 除法前调整)
-
这些指令使用寄存器
AX作为源和目标 -
AAA指令:- 在使用
ADD指令后,使用AAA指令来添加两个未打包的(unpacked) BCD 数字 AL寄存器是AAA指令的隐式源操作数和目标操作数-
AAA指令将AL中的值调整为未打包的 BCD 结果- 如果
AL[3:0] > 9或AF = 1,则AL = AL + 6,AH = 1,并设置CF = 1和AF = 1 - 否则,
CF = 0,AF = 0 - 在任何情况下,
AL[7:4] = 0,将正确的小数位留在AL[3:0]中
- 如果
-
AAA指令可以添加 ASCII 数字,而无需屏蔽掉高四位'3'
- 在使用
-
AAS指令:在 ASCII 减法后调整AX寄存器 AAM指令:- 在对两个一位数的未打包 BCD 数字进行乘法运算后,遵循乘法指令
- 该指令将二进制转换为未打包的 BCD
- 如果
AX中出现介于0000H和0063H之间的二进制数字,该指令将其转换为 BCD
AAD指令:- 出现在除法前
- 要求
AX寄存器在执行前包含两位未打包的 BCD 数字(非 ASCII)
Logic Instructions⚓︎
逻辑运算在低级软件 (low-level software) 中提供二进制位控制,允许设置、清除或补位。
- 低级软件以机器语言或汇编语言形式出现,并且常用于控制系统中的 I/O 设备
逻辑运算始终:
- 清除
OF和CF标志 - 将
SF、ZF和PF标志更改,以反映结果 AF标志的状态是未定义的
当在寄存器或内存位置中操作二进制数据时,最右边的位始终编号为位 0。
所有逻辑指令都会影响标志位,除了标志位不变的 NOT 指令。
下面详细介绍各类逻辑指令。
AND⚓︎
- 执行逻辑乘法,通过真值表说明
- 如果所需速度不是太快,
AND可以替代离散的 AND 门- 通常保留用于嵌入式控制应用
- 在 8086 中,
AND指令通常在约 1μs 内执行- 使用较新版本时,执行速度大大提高
-
可用于实现掩码(masking) 操作,即清除二进制数的位
-
可以使用
AND掩码去除最左边的四个二进制位位置,将 ASCII 编码的数字转换为 BCD
-
-
使用除内存到内存和段寄存器寻址之外的所有模式
OR⚓︎
-
执行逻辑加法,通常称为包含或(inclusive-or) 函数
- 如果有任何输入为 1,就会生成逻辑 1 输出
- 只有当所有输入均为 0 时,输出才为 0
-
OR 门及真值表:
-
使用除段寄存器寻址以外的任何寻址模式
-
使用
OR指令将数字中的某些位(强行)置 1
Exclusive-OR⚓︎
- 与
OR唯一不同的是,当输入为 1, 1 时,异或(exclusive-OR) 产生 0 - 实际上可将规则总结为:若输入都是 0 或者都是 1,则输出为 0;如果输入不同,则输出为 1
- 异或有时被称为比较器(comparator)
-
XOR 门及真值表:
-
使用除段寄存器寻址以外的任何寻址模式
-
可用于反转寄存器或内存位置的某些位
- 当 1 与 \(X\) 进行异或时,结果是 \(\overline{X}\)
- 当 0 与 \(X\) 进行异或时,结果是 \(X\)
-
另一个常见用途是将寄存器清零
Test and Bit Test Instructions⚓︎
-
TEST执行 AND 操作,但只影响标志寄存器的表示测试结果的条件- 工作方式与
CMP指令类似
- 工作方式与
-
通常后面跟着
JZ(如果为零则跳转 (jump if zero))或JNZ(如果不为零则跳转 (jump if not zero))指令 - 目标操作数通常与立即数进行比较
例子
测试 AL 寄存器最右边和最左边的位;用 1 选择最右边的位,128 选择最左边的位
-
CMP和TEST是常用于比较的指令,这些指令被称为条件指令(conditionals) -
TEST same, same用于确定一个符号数是否大于 0 -
TEST EAX, EAX和CMP EAX, 0几乎相同,除了前者的指令比后者更短 -
80386-Pentium 4 包含四个额外的测试指令,用于测试单个位的位置
- 所有位测试指令测试由源操作数选定的目标操作数中的位位置
汇编语言 操作 BT测试目标操作数中由源操作数指定的位 BTC测试目标操作数中由源操作数指定的位,并对该位进行求反操作 BTR测试目标操作数中由源操作数指定的位,并重置(置为 0)该位 BTS测试目标操作数中由源操作数指定的位,并设置(置为 1)该位 -
例子:
BT AX, 4指令测试AX的第 4 位- 测试结果位于进位标志位
- 若第 4 位是 1,
CF置位 - 若第 4 位是 0,
CF清零
-
下面的例子分别展示了在
CX上使用逻辑和位测试指令的位操作
NOT and NEG⚓︎
NOT和NEG可以使用除段寄存器寻址以外的任何寻址模式NOT指令反转一个字节、字或双字的所有位NEG对一个数取二进制补码
NOT被认为是逻辑操作,NEG被认为是算术操作。-
NOT指令不影响任何标志位,而NEG指令影响的标志位有:- 如果操作数 = 0,则
CF= 0,否则CF= 1 OF、SF、ZF、AF和PF标志位根据结果设置
- 如果操作数 = 0,则
-
为什么
NOT指令不修改任何标志位
Shift⚓︎
-
移位指令将数字左移或右移到寄存器或内存位置中
- 还可以执行简单的算术运算,如乘以 2 +n 的幂(左移)和除以 2 -n 的幂(右移
) 。
- 还可以执行简单的算术运算,如乘以 2 +n 的幂(左移)和除以 2 -n 的幂(右移
-
微处理器的指令集包含四种不同的移位指令:
- 两个是逻辑(logical) 移位;两个是算术(arithmetic) 移位
- 语法:
SHL/SAL/SHR/SAR REG/MEM, Count
-
下图展示了这四种移位运算:
SHL和SAL将目标操作数的位向左移动(所以两者执行相同的操作)SHR用 0 填充左边空出来的位SAR用符号位填充左边空出来的位
-
一些例子:
汇编语言 操作 SHL AX,1AX进行逻辑左移 1 位SHR BX,12BX进行逻辑右移 12 位SHR ECX,10ECX进行逻辑右移 10 位SHL RAX,50RAX进行逻辑左移 50 位(64 位模式)SAL DATA1,CL数据段内存位置 DATA1的内容进行算术左移,位移量由CL指定SHR RAX,CLRAX进行逻辑右移,位移量由CL指定(64 位模式)SAR SI,2SI进行算术右移 2 位SAR EDX,14EDX进行算术右移 14 位 -
计数操作数可以是一个立即值或
CL寄存器-
以下示例展示了如何以两种不同方式将
DX寄存器左移 14 位:
-
-
在 16 位或 32 位模式下,移位计数是一个模 32 的计数,即计数范围在 0-31 内
- 在 64 位模式下,移位计数是一个模 64 的计数,即计数范围在 0-63 内
SAR和SHR指令可用于执行目标操作数按 2 的幂次的符号或无符号除法-
负数的
SAR舍入:- 对于负数,
IDIV指令得到的商会被舍入至 0,而SAR指令会将其舍入至负无穷,两者结果并不一致 (inconsistency)
- 对于负数,
-
双精度移位(从 80386 开始)
-
两个指令
: SHLD(左移)和SHRD(右移) ,本质上是跨寄存器的移位 -
每条指令包含三个操作数(
SHLD/SHRD D, S, Count) ,而不是 2 个 - 例如指令
SHLD reg1, reg2, imm8将寄存器reg1和reg2连接起来,并将它们左移由imm8指定的数量 - 两个函数都使用两个 16 位、32 位或 64 位寄存器,或者一个 16 位、32 位或 64 位内存位置和一个寄存器
例子
-
Rotation⚓︎
- 通过在寄存器或内存位置中旋转信息来定位二进制数据,要么从一端到另一端,要么通过进位标志
-
语法:
ROL/ROR/RCL/RCR REG/MEM, Count -
左旋(
ROL)和右旋(ROR)不包括CF标志 - 而进位左旋 (rotate through carry left)(
RCL)和进位右旋 (rotate through carry right)(RCR)将CF标志移入最高或最低有效位 - 旋转计数可以是立即数,或寄存器
CL的值;如果使用后者,其值不会改变 - 旋转指令通常用于将宽数字向左或向右移动
例子
128 位值除以 2 的除法:
Bit Scan Instructions⚓︎
-
位扫描指令在数字中扫描,用于寻找其中的某个位
- 通过移位数字实现
- 在 80386 - Pentium 4 中可用
-
BSF(位向前扫描 (bit scan forward))从最低位向左扫描源数字 BSR(位反向扫描 (bit scan reverse))从最高位向右扫描源数字- 语法:
BSF/BSR REG, REG/MEM - 如果未遇到指定位,则设置零标志(
ZF = 1) - 如果遇到指定位,则清除零标志(
ZF = 0) ,并将该位的位位置编号放入目标操作数中
例子
令 EAX = 60000000H = 0110 0000 0000 0000 0000 0000 0000 0000B
-
BSF EBX, EAXEBX = 29(位 29 是 1)ZF = 0
-
BSR EBX, EAXEBX = 30(位 30 是 1)ZF = 0
-
扩展:
TZCNT(尾部零计数 (tailing zero count))计算尾部零比特的数量LZCNT(前导零计数 (leading zero count))返回前导零比特的数量- 它们和
BSF/BSR之间的主要区别在于:- 如果源操作数 = 0(没有 1 位
) ,则BSF/BSR中目标操作数的内容是未定义的,而TZCNT/LZCNT提供操作数大小作为输出 - 如果源操作数 = 0,
BSF/BSR仅影响ZF,而TZCNT/LZCNT同时设置ZF和CF(ZF = 1, CF = 1) ,否则清零
- 如果源操作数 = 0(没有 1 位
例子
令
EAX = 60000000H = 0110 0000 0000 0000 0000 0000 0000 0000B-
LZCNT EBX, EAXEBX = 1(一个前导位)ZF = 0,CF = 0
-
BSR EBX, EAXEBX = 30(位 30 是 1)ZF = 0
String Comparisons⚓︎
- 字符串指令非常强大,能让程序员相对轻松地操作大量数据块
- 数据块操作通过
MOVS / LODS / STOS / INS / OUTS进行 - 另外一些字符串指令能将内存的一部分,与常量或另一部分内存进行测试,包括
: SCAS(字符串扫描)和CMPS(字符串比较)
SCAS⚓︎
SCAS指令将使用内存操作数指定的字节、字、双字或四字与AL、AX或EAX中的值(隐式操作数)进行比较,然后在EFLAGS中设置状态标志以记录结果- 内存操作数地址根据当前操作模式的地址大小属性从
ES:EDI读取 -
操作数的大小可根据指令选择:
SCASB(字节比较)SCASW(字比较)SCASD(双字比较)
-
SCAS使用方向标志(D)来选择DI/EDI的自动增减操作 SCAS可以在块比较前使用条件重复前缀REPE(相等时重复)或REPNE(不相等时重复)
例子
- 假设内存的一个部分长度为 100 字节,并从位置
BLOCK开始 - 必须测试这个内存部分,以查看是否有任何位置包含
00H - 以下程序展示了如何使用
SCASB指令搜索内存的这一部分以查找00H
MOV DI, OFFSET BLOCK ; address data
CLD ; auto-increment
MOV CX, 100 ; load counter
XOR AL, AL ; clear AL
REPNE SCASB
在 SCASB 指令结束后
- 如果
ZF = 1,则某个位置包含00H - 如果
CX = 0且ZF = 0,则所有数据都不匹配00H
CMPS⚓︎
-
始终比较两个内存区域,数据以字节(
CMPSB) 、字(CMPSW)或双字(CMPSD)的形式进行比较- 由
SI/ESI寻址的数据段内存位置的内容,与由DI/EDI寻址的附加段内存的内容进行比较 CMPS指令会递增 / 递减计数器
- 由
-
通常与
REPE或REPNE前缀一起使用- 替代方案是
REPZ(在零时重复)和REPNZ(在非零时重复)
- 替代方案是
例子
评论区


































