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

这篇具有很好参考价值的文章主要介绍了5.6 汇编语言:汇编高效数组寻址。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

6.1 数组取值操作

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

  .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 可返回数据标号的偏移地址,单位是字节.
    ; 偏移地址代表标号距DS数据段基址的距离.
    xor eax,eax
    mov eax,offset WordVar1
    mov eax,offset DwordVar2
    
    ; 使用 PTR 可指定默认取出参数的大小(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 可以计算数组元素的数量
    xor eax,eax
    mov eax,lengthof ArrayDW             ; eax = 10
    mov eax,lengthof ArrayBT             ; eax = 10
    
    ; 使用 TYPE 可返回按照字节计算的单个元素的大小.
    xor eax,eax
    mov eax,TYPE WordVar1                ; eax = 2
    mov eax,TYPE DwordVar2               ; eax = 4
    mov eax,TYPE ArrayDW                 ; eax = 4
    
    ; 使用 SIZEOF 返回等于LENGTHOF(总元素数)和TYPE(每个元素占用字节)返回值的乘基.
    xor eax,eax
    mov eax,sizeof ArrayBT               ; eax = 10
    mov eax,sizeof ArrayTP               ; eax = 120
    
    invoke ExitProcess,0
  main ENDP
END main

6.2 数组直接寻址

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

  .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
    ; 针对字节的寻址操作
    mov al,[ArrayB]           ; al=10
    mov al,[ArrayB+1]         ; al=20
    mov al,[ArrayB+2]         ; al=30

    ; 针对内存单元字存储操作
    mov bx,[ArrayW]           ; bx=100
    mov bx,[ArrayW+2]         ; bx=200
    mov bx,[ArrayW+4]         ; bx=300

    ; 针对内存单元双字存储操作
    mov eax,[ArrayDW]         ; eax=00000001
    mov eax,[ArrayDW+4]       ; eax=00000002
    mov eax,[ArrayDW+8]       ; eax=00000003
    
    ; 基址加偏移寻址: 通过循环eax的值进行寻址,每次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
    
    ; 基址变址寻址: 循环取出数组中的元素
    mov esi,offset ArrayDW                 ; 数组基址
    mov eax,0                              ; 定义为元素下标
    mov ecx,lengthof ArrayDW               ; 循环次数
  s2:
    mov edi,dword ptr ds:[esi + eax * 4]   ; 取出数值放入edi
    inc eax                                ; 数组递增
    loop s2
    
    invoke ExitProcess,0
  main ENDP
END main

6.3 数组间接寻址

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

  .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寄存器实现寻址.
    mov esi,offset ArrayDW               ; 取出数组基地址
    mov ecx,lengthof ArrayDW             ; 取出数组元素个数
  s1:
    mov eax,dword ptr ds:[esi]           ; 间接寻址
    add esi,4                            ; 每次递增4
    loop s1
    
    ; 第二种: 通过ESP堆栈寄存器,实现寻址.
    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堆栈寄存器,实现寻址.
    push ebp
    mov 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 = 32
    mov ecx,9                        ; 循环9次
  s3: mov edx,dword ptr ss:[esp + eax] ; 寻找栈中元素
    sub eax,4                        ; 每次递减4字节
    loop s3
    
    add esp,36               ; 用完之后修正堆栈
    pop ebp                  ; 恢复ebp

    invoke ExitProcess,0
  main ENDP
END main

6.4 比例因子寻址

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

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

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

  .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 字节
     DWORD 60h,70h,80h,90h,0ah
     DWORD 0bh,0ch,0dh,0eh,0fh
.code
  main PROC
  
    ; 第一种比例因子寻址
    mov esi,0                 ; 初始化因子
    mov ecx,9                 ; 设置循环次数
  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
    
    ; 第三种二维数组寻址
    row_index = 1
    column_index = 2
    
    mov ebx,offset TwoArray            ; 数组首地址
    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

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

  .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 字节
     DWORD 60h,70h,80h,90h,0ah
     DWORD 0bh,0ch,0dh,0eh,0fh
.code
  main PROC
    lea esi,dword ptr ds:[TwoArray]  ; 取基地址
    mov eax,0                        ; 控制外层循环变量
    mov ecx,3                        ; 外层循环次数
  s1:
    push ecx                         ; 保存外循环次数
    push eax
    
    mov ecx,5                        ; 内层循环数
  s2: add eax,4                        ; 每次递增4
    mov edx,dword ptr ds:[esi + eax] ; 定位到内层循环元素
    loop s2
    
    pop eax
    pop ecx
    add eax,20                       ; 控制外层数组
    loop s1 

    invoke ExitProcess,0
  main ENDP
END main

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

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

  .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
    ; 循环让数组中的每一个数加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
    
    ; 循环让数组A与数组B相加后赋值到数组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 数组指针寻址

指针变量是指存储另一个变量的地址的变量。指针类型是指可以存储对另一个变量的指针的数据类型。在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,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            ; 循环次数
  s1:
    mov esi,dword ptr ds:[PtrA]          ; 将指针指向PtrA
    mov ax,word ptr ds:[esi + ebx * 2]   ; 每次递增2字节
    
    mov esi,dword ptr ds:[PtrB]          ; 将指针指向PtrB
    mov eax,dword ptr cs:[esi + ebx * 4] ; 每次递增4字节
    inc esi              ; 基地址递增
    inc ebx              ; 因子递增
    loop s1

    invoke ExitProcess,0
  main 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坐标 * 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]         | 找到数组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,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
      
      ; 模拟实现对二维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,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字节
      imul edx,ecx,2     ; 定位到元素 8
      
      add 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这样还可以提高运算效率.文章来源地址https://www.toymoban.com/news/detail-676513.html

    .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 计算 (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                ; 取出结果

    main ENDP
END main

到了这里,关于5.6 汇编语言:汇编高效数组寻址的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 汇编语言—常见汇编指令汇总

    mov    寄存器 ,数据                如:mov ax ,8 mov   寄存器,寄存器              如:mov ax,bx mov   寄存器,内存单元          如:mov ax,[0] mov   内存单元,寄存器          如:mov [0],ax mov   段寄存器,寄存器          如:mov ds,ax add   寄存器,数据

    2024年02月10日
    浏览(44)
  • 汇编语言第一讲:计算机的组织架构和汇编语言介绍

    第一讲:计算机的组织架构和汇编语言介绍 汇编语言 计算机组织架构 数字电路 术语回顾 数制 数字电路 硬件电路 数字电路的问题 汇编语言的开始 程序的节(sections) 调用操作系统的系统调用 列出文件(Listing files) 汇编和链接 调试汇编程序 反汇编现有的程序 附录 课程资源

    2024年04月09日
    浏览(52)
  • 5.10 汇编语言:汇编过程与结构

    过程的实现离不开堆栈的应用,堆栈是一种后进先出 (LIFO) 的数据结构,最后压入栈的值总是最先被弹出,而新数值在执行压栈时总是被压入到栈的最顶端,栈主要功能是暂时存放数据和地址,通常用来保护断点和现场。 栈是由 CPU 管理的线性内存数组,它使用两个寄存器 (S

    2024年02月11日
    浏览(41)
  • 南京邮电大学汇编语言程序设计实验一(汇编语言语法练习与代码转换)

    排除语法错误:给出的是一个通过比较法完成8位二进制数转换成十进制数送屏幕显示功能的汇编语言源程序,但有很多语法错误。要求实验者按照原样对源程序进行编辑,汇编后,根据TASM给出的信息对源程序进行修改,知道没有语法错误为止。然后进行链接,并执行相应可

    2024年02月08日
    浏览(62)
  • 低级语言汇编真的各个面不如汇编吗?

    今日话题,低级语言汇编真的各个面不如C语言吗?C语言因其可移植性、开发效率和可读性而在各领域广泛使用,市场占有率极高。然而,汇编语言在特定场景下仍然具有独特优势,稳固地占据一席之地。如果你对这方面感兴趣,我可以分享一套包含各类语言和嵌入式行业教

    2024年02月06日
    浏览(48)
  • 在C语言中调用汇编语言的函数

    在C语言中调用汇编文件中的函数,要做的主要工作有两个: 一是在C语言中声明函数原型,并加extern; 二是在汇编中用EXPORT导出函数名,并用该函数名作为汇编代码段的标识,最后用mov pc, lr返回。然后,就可以在C语言中使用该函数了。 从C语言的角度,并不知道该函

    2024年02月14日
    浏览(39)
  • 汇编语言学习笔记六

    CF:进位标志位,产生进位CF=1,否则为0 PF:奇偶位,如010101b,则该数的1有3个,则PF=0,如果该数的1的个数为偶数,则PF=1。 0也是偶数 ZF:在相关指令执行后(运算和逻辑指令,传送指令不影响ZF的值),其结果为0,则ZF=1,否则为0。 SF:符号标志位,如果结果为负,则SF=1,否则为

    2024年02月03日
    浏览(44)
  • 汇编语言中断编程步骤

    1、调用movsb指令将中断处理程序载入内存的指定位置; 1)使用offset指令计算doIntEnd-doInt获取中断处理程序的代码长度; 2)doIntEnd位置使用nop指令。 2、修改中断向量表项为指定位置; 1)使用word ptr确定内存单元; 2)使用es=0来定位中断向量表首地址。 3、编写中断处理程序。

    2024年02月07日
    浏览(43)
  • 汇编语言学习笔记四

    字符是以ASCII码的形式存储的,一个字符对应着8为二进制数,2位16进制数。 所以可以得到对应的字符地址。 根据ASCII码,字符的大写和小写相差一个0010 0000,比如a对应的ASCII码是0110 0001,那么A则对应的是0100 0001,对比可以发现他们只是第5位不同,第5位为1,则是小写,否则

    2024年02月03日
    浏览(63)
  • 汇编语言实验——大数相乘

    1.1实验内容 实现两个十进制大整数的相乘(100位以上),输出乘法运算的结果。 1.2实验环境 Microsoft Visual Studio 2017+masm 32 1.3实验思路 1.3.1数据读入 大数相乘由于输入的数字过大而不能用一个dword来存储,所以需要使用数组来存取每一位,每一位大小范围在0-9中,按位读取输入

    2024年02月09日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包