5.6 汇编语言:汇编高效数组寻址

数组和指针都是用来处理内存地址的操作,二者在C语言中可以互换使用。数组是相同数据类型的一组集合,这些数据在内存中是连续存储的,在C语言中可以定义一维、二维、甚至多维数组。多维数组在内存中也是连续存储的,只是数据的组织方式不同。在汇编语言中,实现多维数组的寻址方式相对于C语言来说稍显复杂,但仍然可行。下面介绍一些常用的汇编语言方式来实现多维数组的寻址。

6.1 数组取值操作

数组取值操作是实现数组寻址的基础,在汇编语言中取值的操作有多种实现方式,这里笔者准备了一个通用案例该案例中包含了,使用OFFSET,PTR,LENGTHOF,TYPE,SIZEOF依次取值的操作细节,读者可自行编译并观察程序的取值过程并以此熟悉这些常用汇编指令集的使用。

  .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataWordVar1 WORD 1234hDwordVar2 DWORD 12345678hArrayBT BYTE 1,2,3,4,5,6,7,8,9,0hArrayDW DWORD 1000,2000,3000,4000,5000,6000,7000,8000,9000,0hArrayTP DWORD 30 DUP(?)
.codemain PROC; 使用 OFFSET 可返回数据标号的偏移地址,单位是字节.; 偏移地址代表标号距DS数据段基址的距离.xor eax,eaxmov eax,offset WordVar1mov eax,offset DwordVar2; 使用 PTR 可指定默认取出参数的大小(DWORD/WORD/BYTE)mov eax,dword ptr ds:[DwordVar2]     ; eax = 12345678hxor eax,eaxmov ax,word ptr ds:[DwordVar2]       ; ax = 5678hmov ax,word ptr ds:[DwordVar2 + 2]   ; ax = 1234h; 使用 LENGTHOF 可以计算数组元素的数量xor eax,eaxmov eax,lengthof ArrayDW             ; eax = 10mov eax,lengthof ArrayBT             ; eax = 10; 使用 TYPE 可返回按照字节计算的单个元素的大小.xor eax,eaxmov eax,TYPE WordVar1                ; eax = 2mov eax,TYPE DwordVar2               ; eax = 4mov eax,TYPE ArrayDW                 ; eax = 4; 使用 SIZEOF 返回等于LENGTHOF(总元素数)和TYPE(每个元素占用字节)返回值的乘基.xor eax,eaxmov eax,sizeof ArrayBT               ; eax = 10mov eax,sizeof ArrayTP               ; eax = 120invoke ExitProcess,0main ENDP
END main

6.2 数组直接寻址

在声明变量名称的后面加上偏移地址即可实现直接寻址,直接寻址中可以通过立即数寻址,也可以通过寄存器相加的方式寻址,如果遇到双字等还可以使用基址变址寻址,这些寻址都属于直接寻址.

  .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataArrayB BYTE 10h,20h,30h,40h,50hArrayW WORD 100h,200h,300h,400hArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
.codemain PROC; 针对字节的寻址操作mov al,[ArrayB]           ; al=10mov al,[ArrayB+1]         ; al=20mov al,[ArrayB+2]         ; al=30; 针对内存单元字存储操作mov bx,[ArrayW]           ; bx=100mov bx,[ArrayW+2]         ; bx=200mov bx,[ArrayW+4]         ; bx=300; 针对内存单元双字存储操作mov eax,[ArrayDW]         ; eax=00000001mov eax,[ArrayDW+4]       ; eax=00000002mov eax,[ArrayDW+8]       ; eax=00000003; 基址加偏移寻址: 通过循环eax的值进行寻址,每次eax递增2mov esi,offset ArrayWmov eax,0mov ecx,lengthof ArrayWs1:mov dx,word ptr ds:[esi + eax]add eax,2loop s1; 基址变址寻址: 循环取出数组中的元素mov esi,offset ArrayDW                 ; 数组基址mov eax,0                              ; 定义为元素下标mov ecx,lengthof ArrayDW               ; 循环次数s2:mov edi,dword ptr ds:[esi + eax * 4]   ; 取出数值放入ediinc eax                                ; 数组递增loop s2invoke ExitProcess,0main ENDP
END main

6.3 数组间接寻址

数组中没有固定的编号,处理此类数组唯一可行的方法是用寄存器作为指针并操作寄存器的值,这种方法称为间接寻址,间接寻址通常可通过ESI实现内存寻址,也可通过ESP实现对堆栈的寻址操作.

  .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
.codemain PROC; 第一种: 通过使用ESI寄存器实现寻址.mov esi,offset ArrayDW               ; 取出数组基地址mov ecx,lengthof ArrayDW             ; 取出数组元素个数s1:mov eax,dword ptr ds:[esi]           ; 间接寻址add esi,4                            ; 每次递增4loop s1; 第二种: 通过ESP堆栈寄存器,实现寻址.mov eax,100                ; eax=1mov ebx,200                ; ebx=2mov ecx,300                ; ecx=3push eax                   ; push 1push ebx                   ; push 2push ecx                   ; push 3mov edx,[esp + 8]          ; EDX = [ESP+8] = 1mov edx,[esp + 4]          ; EDX = [ESP+4] = 2 mov edx,[esp]              ; EDX = [ESP] = 3; 第三种(高级版): 通过ESP堆栈寄存器,实现寻址.push ebpmov ebp,esp                      ; 保存栈地址lea eax,dword ptr ds:[ArrayDW]   ; 获取到ArrayDW基地址; -> 先将数据压栈mov ecx,9                        ; 循环9次s2: push dword ptr ss:[eax]          ; 将数据压入堆栈add eax,4                        ; 每次递增4字节loop s2; -> 在堆栈中取数据mov eax,32                       ; 此处是 4*9=36 36 - 4 = 32mov ecx,9                        ; 循环9次s3: mov edx,dword ptr ss:[esp + eax] ; 寻找栈中元素sub eax,4                        ; 每次递减4字节loop s3add esp,36               ; 用完之后修正堆栈pop ebp                  ; 恢复ebpinvoke ExitProcess,0main ENDP
END main

6.4 比例因子寻址

比例因子寻址是一种常见的寻址方式,通常用于访问数组、矩阵等数据结构。通过指定不同的比例因子,可以实现对多维数组的访问。在使用比例因子寻址时,需要考虑变量的偏移地址、维度、类型以及访问方式等因素,另外比例因子寻址的效率通常比直接寻址要低,因为需要进行一些额外的乘法和加法运算。

使用比例因子寻址可以方便地访问数组或结构体中的元素。在汇编语言中,比例因子可以通过指定一个乘数来实现,这个乘数可以是1、2、4或8,它定义了一个元素相对于数组起始地址的偏移量。

以下例子每个DWORD=4字节,且总元素下标=0-3,得出比例因子3* type arrayDW,并根据比例因子实现对数组的寻址操作。

  .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataArrayW  WORD  1h,2h,3h,4h,5hArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9hTwoArray DWORD 10h,20h,30h,40h,50hRowSize = ($ - TwoArray)            ; 每行所占空间 20 字节DWORD 60h,70h,80h,90h,0ahDWORD 0bh,0ch,0dh,0eh,0fh
.codemain PROC; 第一种比例因子寻址mov esi,0                 ; 初始化因子mov ecx,9                 ; 设置循环次数s1:mov eax,ArrayDW[esi * 4]  ; 通过因子寻址,4 = DWORDadd esi,1                 ; 递增因子loop s1; 第二种比例因子寻址mov esi,0lea edi,word ptr ds:[ArrayW]mov ecx,5s2:mov ax,word ptr ds:[edi + esi * type ArrayW]inc esiloop s2; 第三种二维数组寻址row_index = 1column_index = 2mov ebx,offset TwoArray            ; 数组首地址add ebx,RowSize * row_index        ; 控制寻址行mov esi,column_index               ; 控制行中第几个mov eax, dword ptr ds:[ebx + esi * TYPE TwoArray]invoke ExitProcess,0main ENDP
END main

以二维数组为例,通过比例因子寻址可以模拟实现二维数组寻址操作。比例因子是指访问数组元素时,相邻元素之间在内存中的跨度。在访问二维数组时,需要指定两个比例因子:第一个比例因子表示行数,第二个比例因子表示列数。

  .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataTwoArray DWORD 10h,20h,30h,40h,50hRowSize = ($ - TwoArray)            ; 每行所占空间 20 字节DWORD 60h,70h,80h,90h,0ahDWORD 0bh,0ch,0dh,0eh,0fh
.codemain PROClea esi,dword ptr ds:[TwoArray]  ; 取基地址mov eax,0                        ; 控制外层循环变量mov ecx,3                        ; 外层循环次数s1:push ecx                         ; 保存外循环次数push eaxmov ecx,5                        ; 内层循环数s2: add eax,4                        ; 每次递增4mov edx,dword ptr ds:[esi + eax] ; 定位到内层循环元素loop s2pop eaxpop ecxadd eax,20                       ; 控制外层数组loop s1 invoke ExitProcess,0main ENDP
END main

通过使用比例因子的方式可以对数组进行求和。一般来说,数组求和可以使用循环语句来实现,但在某些情况下,可以通过使用比例因子的方式来提高求和的效率。

在使用比例因子求和时,需要使用汇编指令lea和add。首先,使用lea指令计算出数组元素的地址,然后使用add指令求出数组元素的和。例如,假设有一个100个元素的整型数组a,可以使用以下汇编指令来计算数组元素的和:

  .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataArrayA DWORD 10h,20h,30h,40h,50hArrayB DWORD 10h,20h,30h,40h,50hNewArray DWORD 5 dup(0)
.codemain PROC; 循环让数组中的每一个数加10后回写mov ebx,0mov ecx,5s1:mov eax,dword ptr ds:[ArrayA + ebx * 4]add eax,10mov dword ptr ds:[ArrayA + ebx * 4],eaxinc ebxloop s1; 循环让数组A与数组B相加后赋值到数组NewArraymov ebx,0mov ecx,5s2:mov esi,dword ptr ds:[ArrayA + ebx]add esi,dword ptr ds:[ArrayB + ebx]mov dword ptr ds:[NewArray + ebx],esiadd ebx,4loop s2invoke ExitProcess,0main ENDP
END main

6.5 数组指针寻址

指针变量是指存储另一个变量的地址的变量。指针类型是指可以存储对另一个变量的指针的数据类型。在Intel处理器中,涉及指针时有near指针和far指针两种不同类型,其中Far指针一般用于实模式下的内存管理,而在保护模式下,一般采用Near指针。

在保护模式下,Near指针指的是一个指针变量,它只存储一个内存地址。通常,Near指针的大小为4字节,因此,它可以被存储在单个双字变量中。除此之外,也可以使用void*类型的指针来代表一个指向任何类型的指针。

数组指针是指一个指向数组的指针变量。数组名是数组第一个元素的地址。因此,对数组名求地址就是数组指针。数组指针可以进行地址的加减运算,从而实现对数组中不同元素的访问。

例如,假设有一个大小为10的整型数组a,可以使用以下汇编代码来访问其中一个元素(如a[3]):

lea esi, [a]             ; 将数组a的地址存储到esi中
mov eax, dword [esi+3*4] ; 将a[3]的值存储到eax中

在这个示例中,使用lea指令将数组a的地址存储到esi中。数组a元素的大小为4个字节(即eax大小),所以这里是使用3 * 4来表示a[3]的偏移地址。虽然这里的地址计算看起来比较繁琐,但是通过使用数组指针寻址,可以避免对数组进行循环访问等相对低效的操作。

  .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataArrayA  WORD  1h,2h,3h,4h,5hArrayB DWORD 1h,2h,3h,4h,5hPtrA DWORD offset ArrayA     ; 指针 PtrA --> ArrayAPtrB DWORD offset ArrayB     ; 指针 PTRB --> ArrayB
.codemain PROCmov ebx,0            ; 寻址因子mov ecx,5            ; 循环次数s1:mov esi,dword ptr ds:[PtrA]          ; 将指针指向PtrAmov ax,word ptr ds:[esi + ebx * 2]   ; 每次递增2字节mov esi,dword ptr ds:[PtrB]          ; 将指针指向PtrBmov eax,dword ptr cs:[esi + ebx * 4] ; 每次递增4字节inc esi              ; 基地址递增inc ebx              ; 因子递增loop s1invoke ExitProcess,0main ENDP
END main

6.6 模拟二维数组寻址

在汇编语言中,内存是线性的,只有一个维度,因此,二维数组需要通过模拟方式来实现。常用的方式是使用比例因子寻址和数组指针寻址。以比例因子寻址为例,可以使用汇编指令leamov来模拟实现二维数组的寻址操作。例如,假设有一个二维数组a[3][4],可以使用以下汇编指令来访问数组元素:

mov eax, [a + ebx * 4 + ecx * 4 * 3] ; 访问a[ebx][ecx]元素

其中,a是数组的基地址,ebx是列号,ecx是行号。指定一个比例因子为3,可以将二维数组转换成一维数组,每行的大小为4个字节,因此在访问a[ebx][ecx]时,需要加上行号的偏移量(即ecx * 4 * 3)。

除了使用比例因子寻址,还可以使用数组指针寻址来模拟二维数组的操作。例如,假设有一个二维数组b[3][4],可以使用以下汇编指令来访问数组元素:

lea esi, [b] ; 将数组b的地址存储到esi中
mov eax, dword ptr [esi + ebx * 16 + ecx * 4] ; 访问a[ebx][ecx]元素

在这个示例中,使用lea指令将二维数组b的地址存储到esi中。首先,指针+偏移,将现在想要查的数字所在的行号+列号的位置指向到了数组中,再通过mov指令将数组元素的值存储到eax中。

由于我们的内存本身就是线性的,所以C语言中的二维数组也是线性的,二维数组仅仅只是一维数组的高阶抽象,唯一的区别仅仅只是寻址方式的不同,首先我们先来在Debug模式下编译一段代码,然后分别分析一下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函数,这段代码主要实现给array[0][1]赋值,核心代码如下:

0040106E    8B45 E4                 mov     eax, dword ptr [ebp-1C]        ; eax = x 坐标
00401071    6BC0 0C                 imul    eax, eax, 0C                   ; eax = x * 0c 索引数组
00401074    8D4C05 E8               lea     ecx, dword ptr [ebp+eax-18]    ; ecx = y 坐标
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字节中的5,指向第2个元素

接着来解释一下上方汇编代码:

  • 1.第1条代码: 寄存器EAX是获取到的x的值,此处为C语言中的x=0
  • 2.第2条代码: 其中0C代表一个维度的长度,每个数组有3个元素(3x4=0C)每个元素4字节
  • 3.第3条代码: 寄存器ECX代表数组的y坐标
  • 4.第5条代码: 公式ecx + edx * 4相当于数组首地址 + sizeof(int) * y

寻址公式可总结为: 数组首地址 + sizeof(type[一维数组元素]) * x + sizeof(int) * y 简化后变成数组首地址 + x坐标 + (y坐标 * 4)即可得到寻址地址.

我们来编译function_2函数,一维数组的总大小3*4=0C,并通过寻址公式计算下.

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坐标
0041140A | 8D4C05 E4                | lea ecx,dword ptr ss:[ebp+eax-0x1C]         | ecx = 数组array[0]首地址
0041140E | 8B55 CC                  | mov edx,dword ptr ss:[ebp-0x34]             | edx = y坐标
00411411 | C70491 00000000          | mov dword ptr ds:[ecx+edx*4],0x0            | ecx(数组首地址) + y坐标 * 400411418 | 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]         | 找到数组array[1]的首地址
0041142E | 8B55 B4                  | mov edx,dword ptr ss:[ebp-0x4C]             | 数组b坐标 2
00411431 | C70491 01000000          | mov dword ptr ds:[ecx+edx*4],0x1            | ecx(数组首地址) + b坐标 * 4

根据分析结论,我自己仿照编译器编译特性,仿写了一段汇编版寻址代码,代码很简单,如下:

    .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataMyArrayDWORD DWORD 1,2,3,4,5,6,0hMyArrayWORD DWORD 1,2,3,4,5,6,7,8,9,10,0h
.codemain PROCxor eax,eaxxor ebx,ebxxor ecx,ecxxor edx,edx; 模拟实现对二维4字节数组寻址 寻找 MyArrayDWORD[1][1]; int array[2][3] = {{1,2,3},{4,5,6}}mov eax,0ch         ; 代表每个一维数组长度imul ebx,eax,1      ; 定位维度mov ecx,4           ; 每个四字节imul edx,ecx,1      ; 定位数组add ebx,edx         ; 累加步长mov edx,dword ptr [MyArrayDWORD + ebx]; 模拟实现对二维数组寻址 寻找 MyArrayWORD[1][2]; word array[2][5]={{1,2,3,4,5},{6,7,8,9,10}}xor eax,eaxxor ebx,ebxxor ecx,ecxxor edx,edxmov eax,14h        ; 每个一维长度 4 * 5imul ebx,eax,1     ; 定位到 {6,7,8,9,10}mov ecx,4          ; 定义步长4字节imul edx,ecx,2     ; 定位到元素 8add ebx,edx        ; 累加步长mov edx,dword ptr [MyArrayWORD + ebx]main ENDP
END main

6.7 模拟三维数组寻址

相对于二维数组,三维数组的寻址更加繁琐,但仍然可以使用类似的方式进行模拟。常用的方式是使用比例因子寻址和多级指针。以比例因子寻址为例,我们可以使用数组指针来模拟多维数组的访问操作。假设有一个三维数组c[2][3][4],可以使用以下汇编指令来访问数组元素:

lea esi, [c] ; 将数组c的地址存储到esi中
mov eax, dword ptr [esi + (i*3+j)*16 + k*4] ; 访问c[i][j][k]元素

其中,i表示数组的第一维下标,j表示数组的第二维下标,k表示数组的第三维下标。指定一个比例因子为16,可以将三维数组转换成一维数组,每行的大小为4 * 4 = 16字节,因此在访问c[i][j][k]时,需要加上前两个维度的偏移量(即(i*3+j) * 16),再加上第三个维度的偏移量(即k * 4)。

除了使用比例因子寻址,还可以使用多级指针来模拟三维数组的访问操作。例如,假设有一个三维数组d[2][3][4],可以使用以下汇编指令来访问数组元素:

lea eax, [d] ; 将数组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指令将三维数组d的地址存储到eax中。然后,使用mov指令依次获取d[i]、d[i][j]以及d[i][j][k]的指针并获取其值。其中,i表示数组的第一维下标,j表示数组的第二维下标,k表示数组的第三维下标。

老样子,我们先来编写一段代码,代码中只需要声明一个三维数组即可.

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;
}

首先我们反汇编这段代码,然后观察反汇编代码展示形式,并套入公式看看.针对三维数组 int Array[M][C][H]其下标操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
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,相当于求出sizeof(type[C][H]) * x
  • 3.第3条指令: 求出数组首地址+eax-60也就求出Array[H]位置,并取地址放入ECX
  • 4.第4条指令: 临时[ebp-68]存放Y的值,此处就是得到y的值
  • 5.第5条指令: 左移4位,相当于2^4次方也就是16这一步相当于求sizeof(type[H])的值
  • 6.最后Array[M] + sizeof(type[H])的值求出Array[M][C]的值

接下来我们通过汇编的方式来实现这个寻址过程,为了方便理解,先来写一段C代码,代码中实现定位Array[1][2][3]的位置.

int main(int argc, char* argv[])
{// 对应关系: 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;
}

最终的汇编版如下,这段代码我一开始并没有想出来怎么写,经过不断尝试,终于算是理解了它的寻址方式,并成功实现了仿写,除去此种方式外其实可以完全将imul替换为shl这样还可以提高运算效率.

    .386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataMyArray 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,0hCount DWORD ?x DWORD ?y DWORD ?z DWORD ?.codemain PROCxor eax,eaxxor ebx,ebxxor ecx,ecxxor edx,edx; 定位 Array[1][2][3]mov dword ptr [x],1hmov dword ptr [y],2hmov dword ptr [z],3h; 找到 Array[M]imul eax,dword ptr [x],30h               ; 定位 Array[1] => ([C] * [H]) * 4lea ecx,dword ptr [MyArray + eax]        ; 定位 Array[1] 基地址; 找到 Array[C]mov ebx,dword ptr [y]                    ; 定位 Array[2] => ([C])shl ebx,4h                               ; 2^4=32 计算 (EBX * 16)add ecx,ebx                              ; Array[M] + Array[C]; 找到 Array[H]imul edx,dword ptr[z],4h                 ; Array[H] * 4add ecx,edxmov dword ptr [Count],ecx                ; 取出结果main ENDP
END main

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/a38e7460.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/50001.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决问题:如何在 Git 中查看提交历史

可以使用以下命令查看 Git 中的提交历史: git log这将显示当前分支上的所有提交历史。每个提交的输出包括提交哈希(SHA-1 值)、作者、日期和提交注释。 您也可以添加一些选项,以获取更详细的提交历史: --oneline 显示…

idea的断点调试

1、行断点 首先在代码的最左侧点击会显示红色的圆圈 第二步在main方法中右键选中debug run进行运行 会出现下面图片的情况 出现上图之后,点击console 下一步 这个时候就可以看到调试的结果了 6、方法调用栈:这里显示了该线程调试所经过的所有方法&…

“深入解析JVM内部结构与工作原理:揭秘Java虚拟机的奥秘“

标题:深入解析JVM内部结构与工作原理:揭秘Java虚拟机的奥秘 摘要:本文将深入探讨Java虚拟机(JVM)的内部结构和工作原理,帮助开发者更好地理解JVM的运行机制,从而提高Java程序的性能和稳定性。 …

C语言小白急救 指针进阶讲解1

文章目录 指针一、 字符指针二、 指针数组三、数组指针1.数组的地址2.数组指针3.数组指针的应用 四、数组参数、指针参数1. 一维数组传参2.二维数组传参3.一级指针传参4.二级指针传参 五、函数指针1.函数的地址2.函数指针3.练习 指针 指针的概念: 1.指针就是个变量…

Redis性能配置优化

1、内存优化 Redis的性能取决于可用内存的大小。如果内存不足,Redis将开始交换(swap),这会极大影响性能。因此,首先我们需要确保Redis所用内存的数量合理。 对于合理的内存使用,我们需要对Redis的maxmemory…

跨越边界:从前端切图仔走进iOS开发(Swift版--上集)

本文简介 点赞 关注 收藏 学会了 本文将以前端开发者的视角,和各位工友进入iOS开发的世界。 本文以实战为导向,快速掌握iOS开发这个技能。 无论你是想要扩展技能领域,还是对iOS开发充满好奇,花一个下午学习本文都能打开iOS开…

微服务中间件--http客户端Feign

http客户端Feign http客户端Feigna.Feign替代RestTemplateb.自定义Feign的配置c.Feign的性能优化d.Feign的最佳实践分析e.Feign实现最佳实践(方式二) http客户端Feign a.Feign替代RestTemplate 以前利用RestTemplate发起远程调用的代码: String url "http:…

【ARM】Day9 cortex-A7核I2C实验(采集温湿度)

1. 2、编写IIC协议,采集温湿度值 iic.h #ifndef __IIC_H__ #define __IIC_H__ #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" #include "led.h" /* 通过程序模拟实现I2C总线的时序和协议* GPIOF ---> AHB4* I2C1_S…

Baidu World 2023,定了!

1. 定了,Baidu World 2023 终于定了,今年的 Baidu World 将会于 2023-10-17 日在北京首钢园正式召开,主题为『生成未来 / PROMPT THE WORLD』,这也是近4年来 Baidu World 再次恢复线下举行。 有些小伙伴们如果还不知道什么是 Baid…

tda4 videnc-test-app: CONTINUOUS and STEPWISE FRAMEINTERVALS not supported

/* videnc-test-app */ https://git.ti.com/cgit/jacinto7_multimedia/ git clone https://git.ti.com/git/jacinto7_multimedia/videnc-test-app.git // 编译 ./autogen.sh ./configure --enable-maintainer-mode --buildi386-linux --hostaarch64-none-linux CC/home/share…

GMS基本模块TIN、Solids、Modflow2000/2005、MT3DMS、MODPATH。及其在地下水流动、溶质运移、粒子追踪方面的应用

解决地下水数值模拟技术实施过程中遇到的困难,从而提出切实可行的环境保护措施,达到有效保护环境、防治地下水污染,推动经济社会可持续发展的目的。 (1)水文地质学,地下水数值模拟基础理论;&am…

Kotlin 中的 协程 基础篇

一、什么叫协程 协程可以称为轻量级线程,线程代码块; 二、GlobalScope 协程 CoroutineScope (协程作用域) 的上下文中通过 launch、async 等构造器来启动。GlobalScope ,即全局作用域内启动了一个新的协程,这意味这该协程的生命周期只受整…

CSDN编程题-每日一练(2023-08-23)

CSDN编程题-每日一练(2023-08-23) 一、题目名称:圆小艺二、题目名称:连续子数组的最大和三、题目名称:投篮一、题目名称:圆小艺 时间限制:1000ms内存限制:256M 题目描述: 最近小艺酱渐渐变成了一个圆滑的形状-球!! 小艺酱开始变得喜欢上球! 小艺酱得到n个同心圆。 …

构建智慧停车场:4G DTU实现无线数据高速传输

物联网技术的快速发展使得各种设备能够实现互联互通,无线网络技术给我们的日常生活带来了极大的便利。其中的网络技术如无线WiFi及4G网络已经成为了物联网应用中不可或缺的组成部分。而在工业领域中对4G无线路由器的应用是非常广泛的,人们通过4G工业路由…

意外发现Cortex-M内核带的64bit时间戳,比32bit的DWT时钟周期计数器更方便,再也不用担心溢出问题了

视频: https://www.bilibili.com/video/BV1Bw411D7F5 意外发现Cortex-M内核带的64bit时间戳,比32bit的DWT时钟周期计数器更方便,再也不用担心溢出问题了 介绍: 看参数手册的Debug章节,System ROM Table里面带Timestam…

【Java】基础练习(十)

1.判断邮箱 输入一个电子邮箱,判断是否是正确电子邮箱地址。 正确的邮箱地址: 必须包含 字符,不能是开头或结尾必须以 .com结尾和.com之间必须有其他字符 (1) Email类: package swp.kaifamiao.codes.Java.d0823; /** 输入一个…

Android——基本控件下(十七)

1. 文本切换&#xff1a;TextSwitcher 1.1 知识点 &#xff08;1&#xff09;理解TextSwitcher和ViewFactory的使用。 1.2 具体内容 范例&#xff1a;切换显示当前时间 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools&…

docker安装redis

docker安装redis 一、基本介绍二、前期准备三、docker安装redis3.1 redis镜像拉取3.2 Docker挂载redis配置文件3.3 启动redis容器3.4 验证Redis容器是否正常运行 四、Docker删除Redis容器五、Docker删除Redis镜像 一、基本介绍 Docker 是一个开源的应用容器引擎,参考链接&…

探索最短路径问题:寻找优化路线的算法解决方案

1. 前言&#xff1a;最短路径问题的背景与重要性 在现实生活中&#xff0c;我们常常面临需要找到最短路径的情况&#xff0c;如地图导航、网络路由等。最短路径问题是一个关键的优化问题&#xff0c;涉及在图中寻找两个顶点之间的最短路径&#xff0c;以便在有限时间或资源内找…

时间复杂度与空间复杂度

时间复杂度 时间复杂度是衡量算法运行速度的指标。 常数阶 O(1) 不论算法代码有多少行&#xff0c;只要其中没有循环、递归&#xff0c;按前文所介绍计算方法&#xff0c;其时间复杂度都是Ο(1)。 func calculate(_ num1: Int, _ num2: Int) -> Int {let sum num1 num…