5.6 匯編語言:匯編高效數(shù)組尋址
匯編語言是一種面向機(jī)器的低級語言,用于編寫計(jì)算機(jī)程序。匯編語言與計(jì)算機(jī)機(jī)器語言非常接近,匯編語言程序可以使用符號、助記符等來代替機(jī)器語言的二進(jìn)制碼,但最終會被匯編器編譯成計(jì)算機(jī)可執(zhí)行的機(jī)器碼。
數(shù)組和指針都是用來處理內(nèi)存地址的操作,二者在C語言中可以互換使用。數(shù)組是相同數(shù)據(jù)類型的一組集合,這些數(shù)據(jù)在內(nèi)存中是連續(xù)存儲的,在C語言中可以定義一維、二維、甚至多維數(shù)組。多維數(shù)組在內(nèi)存中也是連續(xù)存儲的,只是數(shù)據(jù)的組織方式不同。在匯編語言中,實(shí)現(xiàn)多維數(shù)組的尋址方式相對于C語言來說稍顯復(fù)雜,但仍然可行。下面介紹一些常用的匯編語言方式來實(shí)現(xiàn)多維數(shù)組的尋址。
### 6.1 數(shù)組取值操作
數(shù)組取值操作是實(shí)現(xiàn)數(shù)組尋址的基礎(chǔ),在匯編語言中取值的操作有多種實(shí)現(xiàn)方式,這里筆者準(zhǔn)備了一個通用案例該案例中包含了,使用OFFSET,PTR,LENGTHOF,TYPE,SIZEOF依次取值的操作細(xì)節(jié),讀者可自行編譯并觀察程序的取值過程并以此熟悉這些常用匯編指令集的使用。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? WordVar1 WORD 1234h
? DwordVar2 DWORD 12345678h
??
? ArrayBT BYTE 1,2,3,4,5,6,7,8,9,0h
? ArrayDW DWORD 1000,2000,3000,4000,5000,6000,7000,8000,9000,0h
? ArrayTP DWORD 30 DUP(?)
.code
? main PROC
? ? ; 使用 OFFSET 可返回?cái)?shù)據(jù)標(biāo)號的偏移地址,單位是字節(jié).
? ? ; 偏移地址代表標(biāo)號距DS數(shù)據(jù)段基址的距離.
? ? xor eax,eax
? ? mov eax,offset WordVar1
? ? mov eax,offset DwordVar2
? ??
? ? ; 使用 PTR 可指定默認(rèn)取出參數(shù)的大小(DWORD/WORD/BYTE)
? ? mov eax,dword ptr ds:[DwordVar2]? ? ?; eax = 12345678h
? ? xor eax,eax
? ? mov ax,word ptr ds:[DwordVar2]? ? ? ?; ax = 5678h
? ? mov ax,word ptr ds:[DwordVar2 + 2]? ?; ax = 1234h
? ??
? ? ; 使用 LENGTHOF 可以計(jì)算數(shù)組元素的數(shù)量
? ? xor eax,eax
? ? mov eax,lengthof ArrayDW? ? ? ? ? ? ?; eax = 10
? ? mov eax,lengthof ArrayBT? ? ? ? ? ? ?; eax = 10
? ??
? ? ; 使用 TYPE 可返回按照字節(jié)計(jì)算的單個元素的大小.
? ? xor eax,eax
? ? mov eax,TYPE WordVar1? ? ? ? ? ? ? ? ; eax = 2
? ? mov eax,TYPE DwordVar2? ? ? ? ? ? ? ?; eax = 4
? ? mov eax,TYPE ArrayDW? ? ? ? ? ? ? ? ?; eax = 4
? ??
? ? ; 使用 SIZEOF 返回等于LENGTHOF(總元素?cái)?shù))和TYPE(每個元素占用字節(jié))返回值的乘基.
? ? xor eax,eax
? ? mov eax,sizeof ArrayBT? ? ? ? ? ? ? ?; eax = 10
? ? mov eax,sizeof ArrayTP? ? ? ? ? ? ? ?; eax = 120
? ??
? ? invoke ExitProcess,0
? main ENDP
END main
```
### 6.2 數(shù)組直接尋址
在聲明變量名稱的后面加上偏移地址即可實(shí)現(xiàn)直接尋址,直接尋址中可以通過立即數(shù)尋址,也可以通過寄存器相加的方式尋址,如果遇到雙字等還可以使用基址變址尋址,這些尋址都屬于直接尋址.
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? ArrayB BYTE 10h,20h,30h,40h,50h
? ArrayW WORD 100h,200h,300h,400h
? ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
.code
? main PROC
? ? ; 針對字節(jié)的尋址操作
? ? mov al,[ArrayB]? ? ? ? ? ?; al=10
? ? mov al,[ArrayB+1]? ? ? ? ?; al=20
? ? mov al,[ArrayB+2]? ? ? ? ?; al=30
? ? ; 針對內(nèi)存單元字存儲操作
? ? mov bx,[ArrayW]? ? ? ? ? ?; bx=100
? ? mov bx,[ArrayW+2]? ? ? ? ?; bx=200
? ? mov bx,[ArrayW+4]? ? ? ? ?; bx=300
? ? ; 針對內(nèi)存單元雙字存儲操作
? ? mov eax,[ArrayDW]? ? ? ? ?; eax=00000001
? ? mov eax,[ArrayDW+4]? ? ? ?; eax=00000002
? ? mov eax,[ArrayDW+8]? ? ? ?; eax=00000003
? ??
? ? ; 基址加偏移尋址: 通過循環(huán)eax的值進(jìn)行尋址,每次eax遞增2
? ? mov esi,offset ArrayW
? ? mov eax,0
? ? mov ecx,lengthof ArrayW
? s1:
? ? mov dx,word ptr ds:[esi + eax]
? ? add eax,2
? ? loop s1
? ??
? ? ; 基址變址尋址: 循環(huán)取出數(shù)組中的元素
? ? mov esi,offset ArrayDW? ? ? ? ? ? ? ? ?; 數(shù)組基址
? ? mov eax,0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; 定義為元素下標(biāo)
? ? mov ecx,lengthof ArrayDW? ? ? ? ? ? ? ?; 循環(huán)次數(shù)
? s2:
? ? mov edi,dword ptr ds:[esi + eax * 4]? ?; 取出數(shù)值放入edi
? ? inc eax? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; 數(shù)組遞增
? ? loop s2
? ??
? ? invoke ExitProcess,0
? main ENDP
END main
```
### 6.3 數(shù)組間接尋址
數(shù)組中沒有固定的編號,處理此類數(shù)組唯一可行的方法是用寄存器作為指針并操作寄存器的值,這種方法稱為間接尋址,間接尋址通??赏ㄟ^ESI實(shí)現(xiàn)內(nèi)存尋址,也可通過ESP實(shí)現(xiàn)對堆棧的尋址操作.
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
.code
? main PROC
? ? ; 第一種: 通過使用ESI寄存器實(shí)現(xiàn)尋址.
? ? mov esi,offset ArrayDW? ? ? ? ? ? ? ?; 取出數(shù)組基地址
? ? mov ecx,lengthof ArrayDW? ? ? ? ? ? ?; 取出數(shù)組元素個數(shù)
? s1:
? ? mov eax,dword ptr ds:[esi]? ? ? ? ? ?; 間接尋址
? ? add esi,4? ? ? ? ? ? ? ? ? ? ? ? ? ? ; 每次遞增4
? ? loop s1
? ??
? ? ; 第二種: 通過ESP堆棧寄存器,實(shí)現(xiàn)尋址.
? ? mov eax,100? ? ? ? ? ? ? ? ; eax=1
? ? mov ebx,200? ? ? ? ? ? ? ? ; ebx=2
? ? mov ecx,300? ? ? ? ? ? ? ? ; ecx=3
? ? push eax? ? ? ? ? ? ? ? ? ?; push 1
? ? push ebx? ? ? ? ? ? ? ? ? ?; push 2
? ? push ecx? ? ? ? ? ? ? ? ? ?; push 3
? ? mov edx,[esp + 8]? ? ? ? ? ; EDX = [ESP+8] = 1
? ? mov edx,[esp + 4]? ? ? ? ? ; EDX = [ESP+4] = 2?
? ? mov edx,[esp]? ? ? ? ? ? ? ; EDX = [ESP] = 3
? ??
? ? ; 第三種(高級版): 通過ESP堆棧寄存器,實(shí)現(xiàn)尋址.
? ? push ebp
? ? mov ebp,esp? ? ? ? ? ? ? ? ? ? ? ; 保存棧地址
? ? lea eax,dword ptr ds:[ArrayDW]? ?; 獲取到ArrayDW基地址
? ? ; -> 先將數(shù)據(jù)壓棧
? ? mov ecx,9? ? ? ? ? ? ? ? ? ? ? ? ; 循環(huán)9次
? s2: push dword ptr ss:[eax]? ? ? ? ? ; 將數(shù)據(jù)壓入堆棧
? ? add eax,4? ? ? ? ? ? ? ? ? ? ? ? ; 每次遞增4字節(jié)
? ? loop s2
? ? ; -> 在堆棧中取數(shù)據(jù)
? ? mov eax,32? ? ? ? ? ? ? ? ? ? ? ?; 此處是 4*9=36 36 - 4 = 32
? ? mov ecx,9? ? ? ? ? ? ? ? ? ? ? ? ; 循環(huán)9次
? s3: mov edx,dword ptr ss:[esp + eax] ; 尋找棧中元素
? ? sub eax,4? ? ? ? ? ? ? ? ? ? ? ? ; 每次遞減4字節(jié)
? ? loop s3
? ??
? ? add esp,36? ? ? ? ? ? ? ?; 用完之后修正堆棧
? ? pop ebp? ? ? ? ? ? ? ? ? ; 恢復(fù)ebp
? ? invoke ExitProcess,0
? main ENDP
END main
```
### 6.4 比例因子尋址
比例因子尋址是一種常見的尋址方式,通常用于訪問數(shù)組、矩陣等數(shù)據(jù)結(jié)構(gòu)。通過指定不同的比例因子,可以實(shí)現(xiàn)對多維數(shù)組的訪問。在使用比例因子尋址時,需要考慮變量的偏移地址、維度、類型以及訪問方式等因素,另外比例因子尋址的效率通常比直接尋址要低,因?yàn)樾枰M(jìn)行一些額外的乘法和加法運(yùn)算。
使用比例因子尋址可以方便地訪問數(shù)組或結(jié)構(gòu)體中的元素。在匯編語言中,比例因子可以通過指定一個乘數(shù)來實(shí)現(xiàn),這個乘數(shù)可以是`1、2、4`或8,它定義了一個元素相對于數(shù)組起始地址的偏移量。
以下例子每個`DWORD=4`字節(jié),且總元素`下標(biāo)=0-3`,得出比例因子`3* type arrayDW`,并根據(jù)比例因子實(shí)現(xiàn)對數(shù)組的尋址操作。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? ArrayW? WORD? 1h,2h,3h,4h,5h
? ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
? TwoArray DWORD 10h,20h,30h,40h,50h
? RowSize = ($ - TwoArray)? ? ? ? ? ? ; 每行所占空間 20 字節(jié)
? ? ?DWORD 60h,70h,80h,90h,0ah
? ? ?DWORD 0bh,0ch,0dh,0eh,0fh
.code
? main PROC
??
? ? ; 第一種比例因子尋址
? ? mov esi,0? ? ? ? ? ? ? ? ?; 初始化因子
? ? mov ecx,9? ? ? ? ? ? ? ? ?; 設(shè)置循環(huán)次數(shù)
? s1:
? ? mov eax,ArrayDW[esi * 4]? ; 通過因子尋址,4 = DWORD
? ? add esi,1? ? ? ? ? ? ? ? ?; 遞增因子
? ? loop s1
? ??
? ? ; 第二種比例因子尋址
? ? mov esi,0
? ? lea edi,word ptr ds:[ArrayW]
? ? mov ecx,5
? s2:
? ? mov ax,word ptr ds:[edi + esi * type ArrayW]
? ? inc esi
? ? loop s2
? ??
? ? ; 第三種二維數(shù)組尋址
? ? row_index = 1
? ? column_index = 2
? ??
? ? mov ebx,offset TwoArray? ? ? ? ? ? ; 數(shù)組首地址
? ? add ebx,RowSize * row_index? ? ? ? ; 控制尋址行
? ? mov esi,column_index? ? ? ? ? ? ? ?; 控制行中第幾個
? ? mov eax, dword ptr ds:[ebx + esi * TYPE TwoArray]
? ??
? ? invoke ExitProcess,0
? main ENDP
END main
```
以二維數(shù)組為例,通過比例因子尋址可以模擬實(shí)現(xiàn)二維數(shù)組尋址操作。比例因子是指訪問數(shù)組元素時,相鄰元素之間在內(nèi)存中的跨度。在訪問二維數(shù)組時,需要指定兩個比例因子:第一個比例因子表示行數(shù),第二個比例因子表示列數(shù)。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? TwoArray DWORD 10h,20h,30h,40h,50h
? RowSize = ($ - TwoArray)? ? ? ? ? ? ; 每行所占空間 20 字節(jié)
? ? ?DWORD 60h,70h,80h,90h,0ah
? ? ?DWORD 0bh,0ch,0dh,0eh,0fh
.code
? main PROC
? ? lea esi,dword ptr ds:[TwoArray]? ; 取基地址
? ? mov eax,0? ? ? ? ? ? ? ? ? ? ? ? ; 控制外層循環(huán)變量
? ? mov ecx,3? ? ? ? ? ? ? ? ? ? ? ? ; 外層循環(huán)次數(shù)
? s1:
? ? push ecx? ? ? ? ? ? ? ? ? ? ? ? ?; 保存外循環(huán)次數(shù)
? ? push eax
? ??
? ? mov ecx,5? ? ? ? ? ? ? ? ? ? ? ? ; 內(nèi)層循環(huán)數(shù)
? s2: add eax,4? ? ? ? ? ? ? ? ? ? ? ? ; 每次遞增4
? ? mov edx,dword ptr ds:[esi + eax] ; 定位到內(nèi)層循環(huán)元素
? ? loop s2
? ??
? ? pop eax
? ? pop ecx
? ? add eax,20? ? ? ? ? ? ? ? ? ? ? ?; 控制外層數(shù)組
? ? loop s1?
? ? invoke ExitProcess,0
? main ENDP
END main
```
通過使用比例因子的方式可以對數(shù)組進(jìn)行求和。一般來說,數(shù)組求和可以使用循環(huán)語句來實(shí)現(xiàn),但在某些情況下,可以通過使用比例因子的方式來提高求和的效率。
在使用比例因子求和時,需要使用匯編指令lea和add。首先,使用lea指令計(jì)算出數(shù)組元素的地址,然后使用add指令求出數(shù)組元素的和。例如,假設(shè)有一個100個元素的整型數(shù)組a,可以使用以下匯編指令來計(jì)算數(shù)組元素的和:
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? ArrayA DWORD 10h,20h,30h,40h,50h
? ArrayB DWORD 10h,20h,30h,40h,50h
? NewArray DWORD 5 dup(0)
.code
? main PROC
? ? ; 循環(huán)讓數(shù)組中的每一個數(shù)加10后回寫
? ? mov ebx,0
? ? mov ecx,5
? s1:
? ? mov eax,dword ptr ds:[ArrayA + ebx * 4]
? ? add eax,10
? ? mov dword ptr ds:[ArrayA + ebx * 4],eax
? ? inc ebx
? ? loop s1
? ??
? ? ; 循環(huán)讓數(shù)組A與數(shù)組B相加后賦值到數(shù)組NewArray
? ? mov ebx,0
? ? mov ecx,5
? s2:
? ? mov esi,dword ptr ds:[ArrayA + ebx]
? ? add esi,dword ptr ds:[ArrayB + ebx]
? ? mov dword ptr ds:[NewArray + ebx],esi
? ? add ebx,4
? ? loop s2
? ? invoke ExitProcess,0
? main ENDP
END main
```
### 6.5 數(shù)組指針尋址
指針變量是指存儲另一個變量的地址的變量。指針類型是指可以存儲對另一個變量的指針的數(shù)據(jù)類型。在`Intel`處理器中,涉及指針時有`near`指針和`far`指針兩種不同類型,其中`Far`指針一般用于實(shí)模式下的內(nèi)存管理,而在保護(hù)模式下,一般采用`Near`指針。
在保護(hù)模式下,Near指針指的是一個指針變量,它只存儲一個內(nèi)存地址。通常,Near指針的大小為4字節(jié),因此,它可以被存儲在單個雙字變量中。除此之外,也可以使用`void*`類型的指針來代表一個指向任何類型的指針。
數(shù)組指針是指一個指向數(shù)組的指針變量。數(shù)組名是數(shù)組第一個元素的地址。因此,對數(shù)組名求地址就是數(shù)組指針。數(shù)組指針可以進(jìn)行地址的加減運(yùn)算,從而實(shí)現(xiàn)對數(shù)組中不同元素的訪問。
例如,假設(shè)有一個大小為10的整型數(shù)組a,可以使用以下匯編代碼來訪問其中一個元素(如a[3]):
```ASM
lea esi, [a]? ? ? ? ? ? ?; 將數(shù)組a的地址存儲到esi中
mov eax, dword [esi+3*4] ; 將a[3]的值存儲到eax中
```
在這個示例中,使用lea指令將數(shù)組a的地址存儲到esi中。數(shù)組a元素的大小為4個字節(jié)(即eax大?。?,所以這里是使用3 * 4來表示a[3]的偏移地址。雖然這里的地址計(jì)算看起來比較繁瑣,但是通過使用數(shù)組指針尋址,可以避免對數(shù)組進(jìn)行循環(huán)訪問等相對低效的操作。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? ArrayA? WORD? 1h,2h,3h,4h,5h
? ArrayB DWORD 1h,2h,3h,4h,5h
??
? PtrA DWORD offset ArrayA? ? ?; 指針 PtrA --> ArrayA
? PtrB DWORD offset ArrayB? ? ?; 指針 PTRB --> ArrayB
.code
? main PROC
??
? ? mov ebx,0? ? ? ? ? ? ; 尋址因子
? ? mov ecx,5? ? ? ? ? ? ; 循環(huán)次數(shù)
? s1:
? ? mov esi,dword ptr ds:[PtrA]? ? ? ? ? ; 將指針指向PtrA
? ? mov ax,word ptr ds:[esi + ebx * 2]? ?; 每次遞增2字節(jié)
? ??
? ? mov esi,dword ptr ds:[PtrB]? ? ? ? ? ; 將指針指向PtrB
? ? mov eax,dword ptr cs:[esi + ebx * 4] ; 每次遞增4字節(jié)
? ? inc esi? ? ? ? ? ? ? ; 基地址遞增
? ? inc ebx? ? ? ? ? ? ? ; 因子遞增
? ? loop s1
? ? invoke ExitProcess,0
? main ENDP
END main
```
### 6.6 模擬二維數(shù)組尋址
在匯編語言中,內(nèi)存是線性的,只有一個維度,因此,二維數(shù)組需要通過模擬方式來實(shí)現(xiàn)。常用的方式是使用比例因子尋址和數(shù)組指針尋址。以比例因子尋址為例,可以使用匯編指令`lea`和`mov`來模擬實(shí)現(xiàn)二維數(shù)組的尋址操作。例如,假設(shè)有一個二維數(shù)組a[3][4],可以使用以下匯編指令來訪問數(shù)組元素:
```ASM
mov eax, [a + ebx * 4 + ecx * 4 * 3] ; 訪問a[ebx][ecx]元素
```
其中,a是數(shù)組的基地址,ebx是列號,ecx是行號。指定一個比例因子為3,可以將二維數(shù)組轉(zhuǎn)換成一維數(shù)組,每行的大小為4個字節(jié),因此在訪問`a[ebx][ecx]`時,需要加上行號的偏移量(`即ecx * 4 * 3`)。
除了使用比例因子尋址,還可以使用數(shù)組指針尋址來模擬二維數(shù)組的操作。例如,假設(shè)有一個二維數(shù)組`b[3][4]`,可以使用以下匯編指令來訪問數(shù)組元素:
```ASM
lea esi, [b] ; 將數(shù)組b的地址存儲到esi中
mov eax, dword ptr [esi + ebx * 16 + ecx * 4] ; 訪問a[ebx][ecx]元素
```
在這個示例中,使用lea指令將二維數(shù)組b的地址存儲到esi中。首先,指針+偏移,將現(xiàn)在想要查的數(shù)字所在的行號+列號的位置指向到了數(shù)組中,再通過mov指令將數(shù)組元素的值存儲到eax中。
由于我們的內(nèi)存本身就是線性的,所以C語言中的二維數(shù)組也是線性的,二維數(shù)組僅僅只是一維數(shù)組的高階抽象,唯一的區(qū)別僅僅只是尋址方式的不同,首先我們先來在`Debug`模式下編譯一段代碼,然后分別分析一下`C編譯器`是如何優(yōu)化的。
```C
void function_1()
{
? int array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
? int x = 0, y = 1;
? array[x][y] = 0;
}
void function_2()
{
? int array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
? int x = 0, y = 1;
? array[x][y] = 0;
? int a = 1, b = 2;
? array[a][b] = 1;
}
```
編譯通過后,我們反匯編`function_1`函數(shù),這段代碼主要實(shí)現(xiàn)給`array[0][1]`賦值,核心代碼如下:
```ASM
0040106E? ? 8B45 E4? ? ? ? ? ? ? ? ?mov? ? ?eax, dword ptr [ebp-1C]? ? ? ? ; eax = x 坐標(biāo)
00401071? ? 6BC0 0C? ? ? ? ? ? ? ? ?imul? ? eax, eax, 0C? ? ? ? ? ? ? ? ? ?; eax = x * 0c 索引數(shù)組
00401074? ? 8D4C05 E8? ? ? ? ? ? ? ?lea? ? ?ecx, dword ptr [ebp+eax-18]? ? ; ecx = y 坐標(biāo)
00401078? ? 8B55 E0? ? ? ? ? ? ? ? ?mov? ? ?edx, dword ptr [ebp-20]? ? ? ? ; edx = 1 二維維度
0040107B? ? C70491 00000000? ? ? ? ?mov? ? ?dword ptr [ecx+edx*4], 0? ? ? ?; 1+1*4=5 4字節(jié)中的5,指向第2個元素
```
接著來解釋一下上方匯編代碼:
?- 1.第1條代碼: 寄存器EAX是獲取到的x的值,此處為C語言中的x=0
?- 2.第2條代碼: 其中0C代表一個維度的長度,每個數(shù)組有3個元素`(3x4=0C)`每個元素4字節(jié)
?- 3.第3條代碼: 寄存器ECX代表數(shù)組的y坐標(biāo)
?- 4.第5條代碼: 公式`ecx + edx * 4`相當(dāng)于`數(shù)組首地址 + sizeof(int) * y`
尋址公式可總結(jié)為: `數(shù)組首地址 + sizeof(type[一維數(shù)組元素]) * x + sizeof(int) * y` 簡化后變成`數(shù)組首地址 + x坐標(biāo) + (y坐標(biāo) * 4)`即可得到尋址地址.
我們來編譯`function_2`函數(shù),一維數(shù)組的總大小`3*4=0C`,并通過尋址公式計(jì)算下.
```ASM
004113F8 | C745 D8 00000000? ? ? ? ?| mov dword ptr ss:[ebp-0x28],0x0? ? ? ? ? ? ?| x = 0
004113FF | C745 CC 01000000? ? ? ? ?| mov dword ptr ss:[ebp-0x34],0x1? ? ? ? ? ? ?| y = 1
00411406 | 6B45 D8 0C? ? ? ? ? ? ? ?| imul eax,dword ptr ss:[ebp-0x28],0xC? ? ? ? | eax = x坐標(biāo)
0041140A | 8D4C05 E4? ? ? ? ? ? ? ? | lea ecx,dword ptr ss:[ebp+eax-0x1C]? ? ? ? ?| ecx = 數(shù)組array[0]首地址
0041140E | 8B55 CC? ? ? ? ? ? ? ? ? | mov edx,dword ptr ss:[ebp-0x34]? ? ? ? ? ? ?| edx = y坐標(biāo)
00411411 | C70491 00000000? ? ? ? ? | mov dword ptr ds:[ecx+edx*4],0x0? ? ? ? ? ? | ecx(數(shù)組首地址) + y坐標(biāo) * 4
00411418 | C745 C0 01000000? ? ? ? ?| mov dword ptr ss:[ebp-0x40],0x1? ? ? ? ? ? ?| a = 1
0041141F | C745 B4 02000000? ? ? ? ?| mov dword ptr ss:[ebp-0x4C],0x2? ? ? ? ? ? ?| b = 2
00411426 | 6B45 C0 0C? ? ? ? ? ? ? ?| imul eax,dword ptr ss:[ebp-0x40],0xC? ? ? ? | eax = 1 * 0c = 0c
0041142A | 8D4C05 E4? ? ? ? ? ? ? ? | lea ecx,dword ptr ss:[ebp+eax-0x1C]? ? ? ? ?| 找到數(shù)組array[1]的首地址
0041142E | 8B55 B4? ? ? ? ? ? ? ? ? | mov edx,dword ptr ss:[ebp-0x4C]? ? ? ? ? ? ?| 數(shù)組b坐標(biāo) 2
00411431 | C70491 01000000? ? ? ? ? | mov dword ptr ds:[ecx+edx*4],0x1? ? ? ? ? ? | ecx(數(shù)組首地址) + b坐標(biāo) * 4
```
根據(jù)分析結(jié)論,我自己仿照編譯器編譯特性,仿寫了一段匯編版尋址代碼,代碼很簡單,如下:
```ASM
? ? .386p
? ? .model flat,stdcall
? ? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? MyArrayDWORD DWORD 1,2,3,4,5,6,0h
? MyArrayWORD DWORD 1,2,3,4,5,6,7,8,9,10,0h
.code
? ? main PROC
? ? ? xor eax,eax
? ? ? xor ebx,ebx
? ? ? xor ecx,ecx
? ? ? xor edx,edx
? ? ??
? ? ? ; 模擬實(shí)現(xiàn)對二維4字節(jié)數(shù)組尋址 尋找 MyArrayDWORD[1][1]
? ? ? ; int array[2][3] = {{1,2,3},{4,5,6}}
? ? ? mov eax,0ch? ? ? ? ?; 代表每個一維數(shù)組長度
? ? ? imul ebx,eax,1? ? ? ; 定位維度
? ? ? mov ecx,4? ? ? ? ? ?; 每個四字節(jié)
? ? ? imul edx,ecx,1? ? ? ; 定位數(shù)組
? ? ??
? ? ? add ebx,edx? ? ? ? ?; 累加步長
? ? ??
? ? ? mov edx,dword ptr [MyArrayDWORD + ebx]
? ? ??
? ? ? ; 模擬實(shí)現(xiàn)對二維數(shù)組尋址 尋找 MyArrayWORD[1][2]
? ? ? ; word array[2][5]={{1,2,3,4,5},{6,7,8,9,10}}
? ? ? xor eax,eax
? ? ? xor ebx,ebx
? ? ? xor ecx,ecx
? ? ? xor edx,edx
? ? ??
? ? ? mov eax,14h? ? ? ? ; 每個一維長度 4 * 5
? ? ? imul ebx,eax,1? ? ?; 定位到 {6,7,8,9,10}
? ? ??
? ? ? mov ecx,4? ? ? ? ? ; 定義步長4字節(jié)
? ? ? imul edx,ecx,2? ? ?; 定位到元素 8
? ? ??
? ? ? add ebx,edx? ? ? ? ; 累加步長
? ? ??
? ? ? mov edx,dword ptr [MyArrayWORD + ebx]
? ? ??
? ? main ENDP
END main
```
### 6.7 模擬三維數(shù)組尋址
相對于二維數(shù)組,三維數(shù)組的尋址更加繁瑣,但仍然可以使用類似的方式進(jìn)行模擬。常用的方式是使用比例因子尋址和多級指針。以比例因子尋址為例,我們可以使用數(shù)組指針來模擬多維數(shù)組的訪問操作。假設(shè)有一個三維數(shù)組`c[2][3][4]`,可以使用以下匯編指令來訪問數(shù)組元素:
```ASM
lea esi, [c] ; 將數(shù)組c的地址存儲到esi中
mov eax, dword ptr [esi + (i*3+j)*16 + k*4] ; 訪問c[i][j][k]元素
```
其中,i表示數(shù)組的第一維下標(biāo),j表示數(shù)組的第二維下標(biāo),k表示數(shù)組的第三維下標(biāo)。指定一個比例因子為16,可以將三維數(shù)組轉(zhuǎn)換成一維數(shù)組,每行的大小為`4 * 4 = 16`字節(jié),因此在訪問`c[i][j][k]`時,需要加上前兩個維度的偏移量(即`(i*3+j) * 16)`,再加上第三個維度的偏移量(`即k * 4`)。
除了使用比例因子尋址,還可以使用多級指針來模擬三維數(shù)組的訪問操作。例如,假設(shè)有一個三維數(shù)組`d[2][3][4]`,可以使用以下匯編指令來訪問數(shù)組元素:
```ASM
lea eax, [d] ; 將數(shù)組d的地址存儲到eax中
mov ebx, [eax + i*4] ; 獲取指向d[i]的指針
mov ecx, [ebx + j*4] ; 獲取指向d[i][j]的指針
mov edx, [ecx + k*4] ; 獲取d[i][j][k]的值
```
在這個示例中,使用lea指令將三維數(shù)組d的地址存儲到eax中。然后,使用mov指令依次獲取`d[i]、d[i][j]`以及`d[i][j][k]`的指針并獲取其值。其中,i表示數(shù)組的第一維下標(biāo),j表示數(shù)組的第二維下標(biāo),k表示數(shù)組的第三維下標(biāo)。
老樣子,我們先來編寫一段代碼,代碼中只需要聲明一個三維數(shù)組即可.
```C
int main(int argc, char* argv[])
{
? ? // int Array[M][C][H]
? ? int Array[2][3][4] = {NULL};
? ? int x = 0;
? ? int y = 1;
? ? int z = 2;
? ? Array[x][y][z] = 3;
? ? return 0;
}
```
首先我們反匯編這段代碼,然后觀察反匯編代碼展示形式,并套入公式看看.針對三維數(shù)組 `int Array[M][C][H]`其下標(biāo)操`Array[x][y][z]=3`
?- 尋址公式為: `Array + sizeof(type[C][H]) * x + sizeof(type[H])*y + sizeof(type)*z`
?- 尋址公式為: `Array + sizeof(Array[C][H]) * x + sizeof(Array[H]) * y + sizeof(Array[M]) * z`
```ASM
00401056? |.? 8B45 9C? ? ? ?mov? ? ?eax, dword ptr [ebp-64]? ? ? ; eax = x
00401059? |.? 6BC0 30? ? ? ?imul? ? eax, eax, 30? ? ? ? ? ? ? ? ?; sizeof(type[C][H]) * x
0040105C? |.? 8D4C05 A0? ? ?lea? ? ?ecx, dword ptr [ebp+eax-60]? ; 取Array[C][H]基地址
00401060? |.? 8B55 98? ? ? ?mov? ? ?edx, dword ptr [ebp-68]? ? ? ; Array[C]
00401063? |.? C1E2 04? ? ? ?shl? ? ?edx, 4? ? ? ? ? ? ? ? ? ? ? ?;
00401066? |.? 03CA? ? ? ? ? add? ? ?ecx, edx? ? ? ? ? ? ? ? ? ? ?;?
00401068? |.? 8B45 94? ? ? ?mov? ? ?eax, dword ptr [ebp-6C]? ? ? ; Array[Z]
0040106B? |.? C70481 030000 mov? ? ?dword ptr [ecx+eax*4], 3
```
接著來解釋一下上方匯編代碼:
?- 1.第1條指令: 得出`eax=x`的值.
?- 2.第2條指令: 其中`eax * 30`,相當(dāng)于求出`sizeof(type[C][H]) * x`
?- 3.第3條指令: 求出`數(shù)組首地址+eax-60`也就求出`Array[H]`位置,并取地址放入`ECX`
?- 4.第4條指令: 臨時`[ebp-68]`存放`Y`的值,此處就是得到y(tǒng)的值
?- 5.第5條指令: 左移4位,相當(dāng)于`2^4`次方也就是`16`這一步相當(dāng)于求`sizeof(type[H])`的值
?- 6.最后`Array[M] + sizeof(type[H])`的值求出`Array[M][C]`的值
接下來我們通過匯編的方式來實(shí)現(xiàn)這個尋址過程,為了方便理解,先來寫一段C代碼,代碼中實(shí)現(xiàn)定位`Array[1][2][3]`的位置.
```C
int main(int argc, char* argv[])
{
? // 對應(yīng)關(guān)系: Array[M][C][H]
? int Array[2][3][4] =?
? {
? ? {
? ? ? { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 }
? ? },
? ? {
? ? ? { 4, 5, 6, 7 }, { 5, 6, 7, 8 }, { 6, 7, 8, 9 }
? ? }
? };
? int x = 1;
? int y = 2;
? int z = 3;
? Array[x][y][z] = 999;
? return 0;
}
```
最終的匯編版如下,這段代碼我一開始并沒有想出來怎么寫,經(jīng)過不斷嘗試,終于算是理解了它的尋址方式,并成功實(shí)現(xiàn)了仿寫,除去此種方式外其實(shí)可以完全將`imul`替換為`shl`這樣還可以提高運(yùn)算效率.
```ASM
? ? .386p
? ? .model flat,stdcall
? ? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? MyArray DWORD 1,2,3,4,2,3,4,5,3,4,5,6,4,5,6,7,5,6,7,8,6,7,8,9,0h
? Count DWORD ?
? x DWORD ?
? y DWORD ?
? z DWORD ?
.code
? ? main PROC
? ? ? xor eax,eax
? ? ? xor ebx,ebx
? ? ? xor ecx,ecx
? ? ? xor edx,edx
? ? ? ; 定位 Array[1][2][3]
? ? ? mov dword ptr [x],1h
? ? ? mov dword ptr [y],2h
? ? ? mov dword ptr [z],3h
? ? ??
? ? ? ; 找到 Array[M]
? ? ? imul eax,dword ptr [x],30h? ? ? ? ? ? ? ?; 定位 Array[1] => ([C] * [H]) * 4
? ? ? lea ecx,dword ptr [MyArray + eax]? ? ? ? ; 定位 Array[1] 基地址
? ? ??
? ? ? ; 找到 Array[C]
? ? ? mov ebx,dword ptr [y]? ? ? ? ? ? ? ? ? ? ; 定位 Array[2] => ([C])
? ? ? shl ebx,4h? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?; 2^4=32 計(jì)算 (EBX * 16)
? ? ? add ecx,ebx? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; Array[M] + Array[C]
? ? ??
? ? ? ; 找到 Array[H]
? ? ? imul edx,dword ptr[z],4h? ? ? ? ? ? ? ? ?; Array[H] * 4
? ? ? add ecx,edx
? ? ??
? ? ? mov dword ptr [Count],ecx? ? ? ? ? ? ? ? ; 取出結(jié)果
? ? main ENDP
END main
```
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/a38e7460.html
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請注明出處!