第六部分 SIB
在386以前16位寻址模式下,要想操作一个多维数组或结构体数组是非常困难的,考虑下面的数组:
Array2D dword Nrow dup (Ncol dup(?))
那么我们要想存取Array2D[rowIndex][colIndex],那么我们该怎么做呢?由于2维数组映射到内存中是一维的,那么Array2D[rowIndex][colIndex]的偏移地址就可以这么计算了: Effect Address = BaseAddress + (rowIndex * colSize + colIndex) * ElementSize;
这里BaseAddress = Array2D,colSize = Ncol,ElementSize = sizeof(dowrd) = 4;我们看看16位寻址模式下我们该如何获取该地址:
mov ax, Ncol
mul rowIndex
add ax, colIndex
add ax, ax ; () * ElementSize,可用shl ax, 2代替
add ax, ax
mov bx, ax ; 16位寻址模式下能用的只有基址寄存器和索引寄存器
这样dword ptr [bx]就是我们需要操作的对象了。当这个数组存储的是结构体的时候,可能还要用[bx + disp]的方式寻址到结构体内的成员。大家可以看到,简单的2维数组的操作就需要这么多指令,而当数组是3维或更大的维度的时候,这个过程的复杂程度还要增加。
386以后,除了寻址大小变成32位,寻址模式上有了很多的改进,所有的寄存器都参与寻址,是一个方面(至少可以省掉上面的一条指令)。最重要的Intel引入的新的寻址方式基址+ 标量索引(SIB)寻址方式,这种寻址方式的引入大大简化了数组内存的存取过程。我们来看看什么是SIB寻址方式:
[base + index * n + displacement]
这跟前面的[寄存器 + displacement]的寻址的一个重要的区别是多了一个Index * n,注意,这个部分是在指令执行的过程中运算的,跟一些在汇编期间进行的乘法运算有着本质的区别(例如[ebx + 标签常量 * 2]等)。这里的base和Index为8种通用寄存器中的任意一种。n的值不能是任意的,只能是2,4,8中的一个。在往下继续解释之前,我们看看上面的算法用32位寻址模式写可以怎么写:
mov eax, Ncol
mul rowIndex
add eax, colIndex
这样dword ptr[eax * 4]就是我们要操作的对象了。当操作一维的结构体数组的时候,SIB的寻址模式将比传统的寻址模式有更大的优势。关于寻址模式和数组,结构体等的操作的例子和分析Art of assembly中的第四章和第五章有详细的介绍,给出这两章的下载:Ch04.pdf Ch05.pdf
6.1 SIB各个bit的涵义
SIB的长度是固定的,只有一个字节,共8bit。我们从SIB寻址方式的定义中就可以大致推测出SIB的结构。
[base + index * n + displacement],n = 2, 4, 8,displacement是后续字节
base和index分别为8