Assembly 数组/串处理指令
80x86家族的处理器提供了几条与数组一起使用的指令。这些指令称为串处理指令。它们使用变址寄存器(ESI和EDI)来执行一个操作,然后这两个寄存器自动地进行增1或减1操作。FLAGS寄存器里的方向标志位(DF) 决定了这些变址寄存器是增加还是减少。有两条指令用来修改方向标志位:
CLD 清方向标志位。这种情况下,变址寄存器是自动增加的。
STD 置方向标志位。这种情况下,变址寄存器是自动减少的。
80x86编程中的一个非常普遍的错误就是忘记了把方向标志位明确地设置为正确的状态。这就经常导致代码大部分情况下能正常工作(当方向标志位恰好就是所需要的状态时),但并不能正常工作在所有情况下。
读写内存
最简单的串处理指令是读或写内存或同时读写内存。它们可以每次读或写一个字节,一个字或一个双字。图5.7中的小段伪码展示了这些指令的作用。这有点需要注意的。首先,ESI是用来读的,而EDI是用来写的。如果你能记得SI代表Source Index,源变址寄存器和DI代表Destination Index,目的变址寄存器,那么这个就很容易记住了。其次,注意包含数据的寄存器是固定的(AL,AX或EAX)。最后,注意存入串指令使用ES来决定需要写的段,而不是DS。在保护模式下,这通常不是问题,因为它只有一个数据段,而ES应自动地初始化为引用DS(和DS一样)。但是,在实模式下,将ES初始化为正确的段值对于程序员来说是非常重要的3。图5.8展示了一个使用这些指令将一个数组复制到另一数组的例子。
LODSx和STOSx指令的联合使用(如图5.8中的13和14行)是非常普遍的。事实上,一条MOVSx串处理指令可以用来完成这个联合使用的功能。图5.9描述了这些指令执行的操作。图5.8的第13和14行可以用MOVSD指令来替代,能得到同样的效果。唯一的区别就是在循环时EAX寄存器根本就不会被使用。
REP前缀指令
80x86家族提供了一个特殊的前缀指令,称为REP,它可以与上面的串处理指令一同使用。这个前缀告诉CPU重复执行下条串处理指令一个指定的次数。ECX寄存器用来计算重复的次数(和在LOOP指令中的使用是一样的)。使用REP前缀,在图5.8中的12到15行的循环体可以替换成一行:
rep movsd
图5.10展示了另一个例子:得到一个零数组。
串比较指令
图5.11展示了几个新的串处理指令:它们可以用来比较内存和内存或内存和寄存器。在比较和查找数组方面,它们是很有用的。它们会像CMP指令一样设置FLAGS寄存器。CMPSx 指令比较相应的内存空间,而SCASx 根据一指定的值扫描内存空间。
图5.12展示了一个代码小片断:在一个双字数组中查找数字12。行10里的SCASD指令总是对EDI进行加4操作,即使找到了需要的数值。因此,如果你想得到数组中数12的地址,就必须用EDI减去4(正如行16所做的)。
REPx前缀指令
还有其它可以用在串比较指令中的,像REP一样的前缀指令。图5.13展示了两个新的前缀并描述了它们的操作。REPE 和REPZ作用是一样的(REPNE和REPNZ也是一样)。如果重复的串比较指令因为比较的结果而终止了,变址寄存器同样会进行增量操作而ECX也会进行减1操作;但是FLAGS寄存器将仍然保持着重复终止时的状态。因此,使用ZF标志位来确定重复的比较是因为一次比较而结束,还是因为ECX等于0而结束是可能的。
图5.14展示了一个样例子代码片断:确定两个内存块是否相等。例子中行7中的JE指令是用来检查前面指令的结果。如果重复的比较是因为它找到了两个不相等的字节而终止,那么ZF标志位就为0,也就不会执行分支;但是,如果比较是因为ECX等于0而结束,那么ZF标志位就为1,而且代码将分支到equal标号处。