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

微软技术分享 微软全球最有价值专家
全栈领域优质创作者
博客专家认证
2023-11-17 09:45:40

数组和指针都是用来处理内存地址的操作,二者在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这样还可以提高运算效率.

    .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
...全文
68 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
【内容简介】 汇编语言是各种CPU所提供的机器指令的助记符的集合,人们可以用汇编语言直接控制硬件系统进行工作。汇编语言是很多相关课程(如:数据结构、操作系统、微机原理等)的重要基础。为了更好地引导、帮助读者学习汇编语言,作者以循序渐进的方式精心创作了这本书。本书具有如下特点:采用全新的结构对课程的内容进行了组织,对知识进行最小化分割,为读者构造了循序渐进的学习线索;在深入本质的层面上对汇编语言进行讲解;对关键环节进行深入的剖析。 本书可用作大学计算机专业本科生的汇编教材及希望深入学习计算机科学的读者的自学教材。 【目录信息】 第1章基础知识 1.1 机器语言 1.2 汇编语言的产生 1.3 汇编语言的组成 1.4 存储器 1.5 指令和数据 1.6 存储单元 1.7 CPU对存储器的读写 1.8 地址总线 1.9 数据总线 1.10 控制总线 1.11 内存地址空间(概述) 1.12 主板 1.13 接口卡 1.14 各类存储器芯片 1.15 内存地址空间 第2章寄存器(CPU工作原理) 2.1 通用寄存器 2.2 字在寄存器中的存储 2.3 几条汇编指令 2.4 物理地址 2.5 16位结构的CPU 2.6 8086CPU给出物理地址的方法 2.7 "段地址x16+偏移地址=物理地址"的本质含义 2.8 段的概念 2.9 段寄存器 2.10 CS和IP 2.11 修改CS.IP的指令 2.12 代码段 实验1 查看CPU和内存,用机器指令和汇编指令编程 第3章寄存器(内存访问) 3.1 内存中字的存储 3.2 DS和[address] 3.3 字的传送 3.4 mov.add.sub指令 3.5 数据段 3.6 栈 3.7 CPU提供的栈机制 3.8 栈顶超界的问题 3.9 push.pop指令 3.10 栈段 实验2 用机器指令和汇编指令编程 第4章第1个程序 4.1 一个源程序从写出到执行的过程 4.2 源程序 4.3 编辑源程序 4.4 编译 4.5 连接 4.6 以简化的方式进行编译和连接 4.7 1.exe的执行 4.8 可执行文件中的程序装入内存并运行的原理 4.9 程序执行过程的跟踪 实验3 编程.编译.连接.跟踪 第5章[bx]和loop指令 5.1 [bx] 5.2 Loop指令 5.3 在Debug中跟踪用loop指令实现的循环程序 5.4 Debug和汇编编译器Masm对指令的不同处理 5.5 loop和[bx]的联合应用 5.6 段前缀 5.7 一段安全的空间 5.8 段前缀的使用 实验4 [bx]和loop的使用 第6章包含多个段的程序 6.1 在代码段中使用数据 6.2 在代码段中使用栈 6.3 将数据.c代码.c栈放入不同的段 实验5 编写,调试具有多个段的程序 第7章更灵活的定位内存地址的方法 7.1 and和or指令 7.2 关于ASCII码 7.3 以字符形式给出的数据 7.4 大小写转换的问题 7.5 [bx+idata] 7.6 用[bx+idata]的方式进行数组的处理 7.7 SI和DI 7.8 [bx+si]和[bx+di] 7.9 [bx+si+idata]和[bx+di+idata] 7.10 不同的寻址方式的灵活应用 实验6 实践课程中的程序 第8章数据处理的两个基本问题 8.1 bx,si,di,bp 8.2 机器指令处理的数据所在位置 8.3 汇编语言中数据位置的表达 8.4 寻址方式 8.5 指令要处理的数据有多长? 8.6 寻址方式的综合应用 8.7 div指令 8.8 伪指令dd 8.9 dup 实验7寻址方式在结构化数据访问中的应用 第9章转移指令的原理 9.1 操作符offset 9.2 jmp指令 9.3 依据位移进行转移的jmp指令 9.4 转移的目的地址在指令中的jmp指令 9.5 转移地址在寄存器中的jmp指令 9.6 转移地址在内存中的jmp指令 9.7 jcxz指令 9.8 loop指令 9.9 根据位移进行转移的意义 9.10 编译器对转移位移超界的检测 实验8 分析一个奇怪的程序 实验9 根据材料编程 第10章call和ret指令 10.1 ret和retf 10.2 call指令 10.3 依据位移进行转移的call指令 10.4 转移的目的地址在指令中的call指令 10.5 转移地址在寄存器中的call指令 10.6 转移地址在内存中的call指令 10.7 all和ret的配合使用 10.8 mul指令 10.9 模块化程序设计 10.10 参数和结果传递的问题 10.11 批量数据的传递 10.12 寄存器冲突的问题 实验10 编写子程序 课程设计1 第11章标志寄存器 11.1 ZF标志 11.2 PF标志 11.3 SF标志 11.4 CF标志 11.5 OF标志 11.6 adc指令 11.7 sbb指令 11.8 cmp指令 11.9 检测比较结果的条件转移指令 11.10 DF标志和串传送指令 11.11 pushf和popf 11.12 标志寄存器在Debug中的表示 实验11 编写子程序 第12章内中断 12.1 内中断的产生 12.2 中断处理程序 12.3 中断向量表 12.4 中断过程 12.5 中断处理程序 12.6 除法错误中断的处理 12.7 编程处理0号中断 12.8 安装 12.9 do0 12.10 设置中断向量 12.11 单步中断 12.12 响应中断的特殊情况 实验12 编写0号中断的处理程序 第13章int指令 13.1 int指令 13.2 编写供应用程序调用的中断例程 13.3 对int iret和栈的深入理解 13.4 BIOS和DOS所提供的中断例程 13.5 BIOS和DOS中断例程的安装过程 13.6 BIOS中断例程应用 13.7 DOS中断例程应用 实验13 编写应用中断例程 第14章端口 14.1 端口的读写 14.2 CMOS RAM芯片 14.3 shl和shr指令 14.4 CMOS RAM中存储的时间信息 实验14访问CMOS 第15章外中断 15.1 接口芯片和端口 15.2 外中断信息 15.3 PC机键盘的处理过程 15.4 编写int 9中断例程 15.5 安装新的int 9中断例程 实验15 安装新的int 9中断例程指令系统总结 第16章直接定址表 16.1 描述了单元长度的标号 16.2 在其他段中使用数据标号 16.3 直接定址表 16.4 程序入口地址的直接定址表 实验16 编写包含多个功能子程序的中断例程 第17章使用BIOS进行键盘输入和磁盘读写 17.1 int 9中断例程对键盘输入的处理 17.2 使用int 16h中断例程读取键盘缓冲区 17.3 字符串的输入 17.4 应用int 13h中断例程对磁盘进行读写 实验17 编写包含多个功能子程序的中断例程 课程设计2
本书分为三部分。第一部分是基础部分,以8086/8088为背景,以DOS和PC兼容机为软硬件平台,以MASM和TASM为汇编器,介绍汇编语言的有关概念,讲解汇编语言程序设计技术。第二部分是提高部分,以80386为背景,以新一代微处理器Pentium为目标,细致和通俗地介绍了保护方式下的有关概念,系统和详细地讲解了保护方式下的编程技术,真实和生动地展示了保护方式下的编程细节。第三部分是上机实验指导。 本书的第一部分适合初学者,可作为学习汇编语言程序设计的教材。本书的第二部分适合已基本掌握8086/8088汇编语言的程序员,可作为学习保护方式编程技术的教材或参考书,也可作为其他人员了解高档微处理器和保护方式编程技术的参考书,还可作为程序员透彻地了解Windows程序设计技术的参考书。 第一部分 基础部分 第1章 绪论 1.1 汇编语言概述 1.1.1 汇编语言 1.1.2 汇编语言的特点 1.1.3 恰当地使用汇编语言 1.2 数据的表示和类型 1.2.1 数值数据的表示 1.2.2 非数值数据的表示 1.2.3 基本数据类型 1.3 Intel系列CPU简介 1.3.1 8位微处理器 1.3.2 16位微处理器 1.3.3 32位微处理器 1.3.4 Pentium和Pentium Pro 1.4 习题 第2章 8086/8088寻址方式和指令系统 2.1 8086/8088寄存器组 2.1.1 8086/8088 CPU寄存器组 2.1.2 标志寄存器 2.2 存储器分段和地址的形成 2.2.1 存储单元的地址和内容 2.2.2 存储器的分段 2.2.3 物理地址的形成 2.2.4 段寄存器的引用 2.3 8086/8088的寻址方式 2.3.1 立即寻址方式 2.3.2 寄存器寻址方式 2.3.3 直接寻址方式 2.3.4 寄存器间接寻址方式 2.3.5 寄存器相对寻址方式 2.3.6 基址加变址寻址方式 2.3.7 相对基址加变址寻址方式 2.4 8086/8088指令系统 2.4.1 指令集说明 2.4.2 数据传送指令 2.4.3 堆栈操作指令 2.4.4 标志操作指令 2.4.5 加减运算指令 2.4.6 乘除运算指令 2.4.7 逻辑运算和移位指令 2.4.8 转移指令 2.5 习题 第3章 汇编语言及其程序设计初步 3.1 汇编语言的语句 3.1.1 语句的种类和格式 3.1.2 数值表达式 3.1.3 地址表达式 3.2 变量和标号 3.2.1 数据定义语句 3.2.2 变量和标号 3.3 常用伪指令语句和源程序组织 3.3.1 符号定义语句 3.3.2 段定义语句 3.3.3 汇编语言源程序的组织 3.4 顺序程序设计 3.4.1 顺序程序举例 3.4.2 简单查表法代码转换 3.4.3 查表法求函数值 3.5 分支程序设计 3.5.1 分支程序举例 3.5.2 利用地址表实现多向分支 3.6 循环程序设计 3.6.1 循环程序举例 3.6.2 多重循环程序举例 3.7 习题 第4章 子程序设计和DOS功能调用 4.1 子程序设计 4.1.1 过程调用和返回指令 4.1.2 过程定义语句 4.1.3 子程序举例 4.1.4 子程序说明信息 4.1.5 寄存器的保护与恢复 4.2 主程序与子程序间的参数传递 4.2.1 利用寄存器传递参数 4.2.2 利用约定存储单元传递参数 4.2.3 利用堆栈传递参数 4.2.4 利用CALL后续区传递参数 4.3 DOS功能调用及应用 4.3.1 DOS功能调用概述 4.3.2 基本I/O功能调用 4.3.3 应用举例 4.4 磁盘文件管理及应用 4.4.1 DOS磁盘文件管理功能调用 4.4.2 应用举例 4.5 子程序的递归和重入 4.5.1 递归子程序 4.5.2 可重入子程序 4.6 习题 第5章 输入输出与中断 5.1输 入和输出的基本概念 5.1.1 I/O端口地址和I/O指令 5.1.2 数据传送方式 5.1.3 存取RT/CMOS RAM 5.2 查询方式传送数据 5.2.1 查询传送方式 5.2.2 读实时钟 5.2.3 查询方式打印输出 5.3 中断 5.3.1 中断和中断传送方式 5.3.2 中断向量表 5.3.3 中断响应过程 5.3.4 外部中断 5.3.5 内部中断 5.3.6 中断优先级和中断嵌套 5.3.7 中断处理程序的设计 5.4 基本输入输出系统BIOS 5.4.1 基本输入输出系统BIOS概述 5.4.2 键盘输入 5.4.3 显示输出 5.4.4 打印输出 5.5 软中断处理程序举例 5.5.1 打印I/O程序 5.5.2 时钟显示程序 5.6 习题 第6章 简单应用程序的设计 6.1 字符串处理 6.1.1 字符串操作指令 6.1.2 重复前缀 6.1.3 字符串操作举例 6.2 十进制数算术运算调整指令及应用 6.2.1 组合BCD码的算术运算调整指令 6.2.2 未组合BCD码的算术运算调整指令 6.2.3 应用举例 6.3 DOS程序段前缀和特殊情况处理程序 6.3.1 DOS程序段前缀PSP 6.3.2 对Ctrl+C键和Ctrl+Break键的处理 6.4 TSR程序设计举例 6.4.1 驻留的时钟显示程序 6.4.2 热键激活的TSR程序 6.5 习题 第7章 高级汇编语言技术 7.1 结构和记录 7.1.1 结构 7.1.2 记录 7.2 宏 7.2.1 宏指令的定义和使用 7.2.2 宏指令的用途 7.2.3 宏指令中参数的使用 7.2.4 特殊的宏运算符 7.2.5 宏与子程序的区别 7.2.6 与宏有关的伪指令 7.2.7 宏定义的嵌套 7.3 重复汇编 7.3.1 伪指令REPT 7.3.2 伪指令IRP 7.3.3 伪指令IRPC 7.4 条件汇编 7.4.1 条件汇编伪指令 7.4.2 条件汇编与宏结合 7.5 源程序的结合 7.5.1 源程序的结合 7.5.2 宏库的使用 7.6 习题 第8章 模块化程序设计技术 8.1 段的完整定义 8.1.1 完整的段定义 8.1.2 关于堆栈段的说明 8.1.3 段组的说明和使用 8.2 段的简化定义 8.2.1 存储模型说明伪指令 8.2.2 简化的段定义伪指令 8.2.3 存储模型说明伪指令的隐含动作 8.3 模块间的通信 8.3.1 伪指令PUBLIC和伪指令EXTRN 8.3.2 模块间的转移 8.3.3 模块间的信息传递 8.4 子程序库 8.4.1 子程序库 8.4.2 建立子程序库 8.4.3 使用举例 8.5 编写供Turbo C调用的函数 8.5.1 汇编格式的编译结果 8.5.2 汇编模块应该遵守的约定 8.5.3 参数传递和寄存器保护 8.5.4 举例 8.6 习题 第二部分 提高部分 第9章 80386程序设计基础 9.1 80386寄存器 9.1.1 通用寄存器 9.1.2 段寄存器 9.1.3 指令指针和标志寄存器 9.2 80386存储器寻址 9.2.1 存储器寻址基本概念 9.2.2 灵活的存储器寻址方式 9.2.3 支持各种数据结构 9.3 80386指令集 9.3.1 数据传送指令 9.3.2 算术运算指令 9.3.3 逻辑运算和移位指令 9.3.4 控制转移指令 9.3.5 串操作指令 9.3.6 高级语言支持指令 9.3.7 条件字节设置指令 9.3.8 位操作指令 9.3.9 处理器控制指令 9.4 实方式下的程序设计 9.4.1 说明 9.4.2 实例 9.5 习题 第10章 保护方式下的80386及其编程 10.1 保护方式简述 10.1.1 存储管理机制 10.1.2 保护机制 10.2 分段管理机制 10.2.1 段定义和虚拟地址到线性地址转换 10.2.2 存储段描述符 10 2.3 全局和局部描述符表 10.2.4 段选择子 10.2.5 段描述符高速缓冲寄存器 10.3 80386控制寄存器和系统地址寄存器 10.3.1 控制寄存器 10 3.2 系统地址寄存器 10.4 实方式与保护方式切换实例 10.4.1 演示实方式和保护方式切换的实例(实例一) 10.4.2 演示32位代码段和16位代码段切换的实例(实例二) 10.5 任务状态段和控制门 10.5.1 系统段描述符 10.5.2 门描述符 10.5.3 任务状态段 10.6 控制转移 10.6.1 任务内无特权级变换的转移 10.6.2 演示任务内无特权级变换转移的实例(实例三) 10.6.3 任务内不同特权级的变换 10.6.4 演示任务内特权级变换的实例(实例四) 10.6.5 任务切换 10.6.6 演示任务切换的实例(实例五) 10.7 80386的中断和异常 10.7.1 80386的中断和异常 10.7.2 异常类型 10.7.3 中断和异常的转移方法 10.7.4 演示中断处理的实例(实例六) 10.7.5 演示异常处理的实例(实例七) 10.7.6 各种转移途径小结 10.8 操作系统类指令 10.8.1 实方式和任何特权级下可执行的指令 10.8.2 实方式及特权级0下可执行的指令 10 8.3 只能在保护方式下执行的指令 10.8.4 显示关键寄存器内容的实例(实例八) 10.8.5 特权指令 10.9 输入/输出保护 10.9.1 输入/输出保护 10.9.2 重要标志保护 10.9.3 演示输入/输出保护的实例(实例九) 10.10 分页管理机制 10.10.1 存储器分页管理机制 10.10.2 线性地址到物理地址的转换 10.10.3 页级保护和虚拟存储器支持 10.10.4 页异常 10.10.5 演示分页机制的实例(实例十) 10.11 虚拟8086方式 10.11.1 V86方式 10.11.2 进入和离开V86方式 10.11.3 演示进入和离开V86方式的实例(实例十一) 10.11.4 V86方式下的敏感指令 10.12 习题 第11章 80486及Pentium程序设计基础 11.1 80486程序设计基础 11.1.1 寄存器 11.1.2 指令系统 11.1.3 片上超高速缓存 11.2 80486对调试的支持 11 2.1 调试寄存器 11.2.2 演示调试故障/陷阶的实例 11.3 Pentium程序设计基础 11.3.1 寄存器 11.3.2 指令系统 11.3.3 处理器的识别 11.3.4 片上超高速缓存 11.4 基于Pentium的程序优化技术 11.4.1 流水线优化技术 11.4.2 分支优化技术 11.4.3 超高速缓存代化技术 11.5 习题 第三部分 上机实验指导 第12章 实验指导 12.1 实验的一般步骤 12.2 汇编器和连接器的使用 12.2.1 MASM的使用 12.2.2 LINK的使用 12.2.3 TASM的使用 12.2.4 TLINK的使用 12.3 调试器DEBUG的使用 12.3.1 启动和退出DEBUG 12.3.2 命令一览 12.3.3 利用DEBUG调试程序 12.4 Turbo Debugger的使用 12.4.1 启动和退出TD 12.4.2 利用TD调试汇编程序 参考文献 附录 Pentium指令与标志参考表
第1章 基础知识 1.1 机器语言 1.2 汇编语言的产生 1.3 汇编语言的组成 1.4 存储器 1.5 指令和数据 1.6 存储单元 1.7 CPU对存储器的读写 1.8 地址总线 1.9 数据总线 1.10 控制总线 1.11 内存地址空间(概述) 1.12 主板 1.13 接口卡 1.14 各类存储器芯片 1.15 内存地址空间 第2章 寄存器 2.1通用寄存器 2.2字在寄存器中的存储 2.3几条汇编指令 2.4物理地址 2.516位结构的CPU 2.68086CPU给出物理地址的方法 2.7“段地址×16+偏移地址=物理地址” 的本质含义 2.8段的概念 2.9段寄存器 2.10CS和IP 2.11修改CS、IP的指令 2.12代码段 实验1查看CPU和内存,用机器指令 和汇编指令编程 第3章寄存器(内存访问) 3.1内存中字的存储 3.2DS和(address) 3.3字的传送 3.4mov、add、sub指令 3.5数据段 3.6栈 3.7CPU提供的栈机制 3.8栈顶超界的问题 3.9push、pop指令 3.10栈段 实验2用机器指令和汇编指令编程 第4章第一个程序 4.1一个源程序从写出到执行的过程 4.2源程序 4.3编辑源程序 4.4编译 4.5连接 4.6以简化的方式进行编译和连接 4.71.exe的执行 4.8谁将可执行文件中的程序装载进入 内存并使它运行? 4.9程序执行过程的跟踪 实验3编程、编译、连接、跟踪 第5章(BX)和loop指令 5.1(BX) 5.2Loop指令 5.3在Debug中跟踪用loop指令实现的 循环程序 5.4Debug和汇编编译器masm对指令的 不同处理 5.5loop和(bx)的联合应用 5.6段前缀 5.7一段安全的空间 5.8段前缀的使用 实验4(bx)和loop的使用 第6章 包含多个段的程序 6.1在代码段中使用数据 6.2在代码段中使用栈 6.3将数据、代码、栈放入不同的段 实验5编写、调试具有多个段的程序 第7章更灵活的定位内存地址的 方法 7.1anol和or指令 7.2关于ASCII码 7.3以字符形式给出的数据 7.4大小写转换的问题 7.5(bx+idata) 7.6用(bx+idata)的方式进行数组的 处理 7.7SI和DI 7.8(bx+si)和(bx+di) 7.9(bx+si+idata)和(bx+di+idata) 7.10不同的寻址方式的灵活应用 实验6实践课程中的程序 第8章数据处理的两个基本问题 8.1bx、si、di和bp 8.2机器指令处理的数据在什么地方 8.3汇编语言中数据位置的表达 8.4寻址方式 8.5指令要处理的数据有多长 8.6寻址方式的综合应用 8.7div指令 8.8伪指令dd 8.9dup 实验7寻址方式在结构化数据访问中的 应用 第9章转移指令的原理 9.1操作符offset 9.2jmp指令 9.3依据位移进行转移的jmp指令 9.4转移的目的地址在指令中的jmp 指令 9.5转移地址在寄存器中的jmp指令 9.6转移地址在内存中的jmp指令 9.7jcxz指令 9.8loop指令 9.9根据位移进行转移的意义 9.10编译器对转移位移超界的检测 实验8分析一个奇怪的程序 实验9根据材料编程 第10章CALL和RET指令 10.1ret和retf 10.2call指令 10.3依据位移进行转移的call指令 10.4转移的目的地址在指令中的call 指令 10.5转移地址在寄存器中的call指令 10.6转移地址在内存中的call指令 10.7call和ret的配合使用 10.8mul指令 10.9模块化程序设计 10.10参数和结果传递的问题 10.11批量数据的传递 10.12寄存器冲突的问题 实验10编写子程序 课程设计1 第11章标志寄存器 11.1ZF标志 11.2PF标志 11.3SF标志 11.4CF标志 11.5OF标志 11.6adc指令 11.7sbb指令 11.8cmp指令 11.9检测比较结果的条件转移指令 11.10DF标志和串传送指令 11.11pushf和popf 11.12标志寄存器在Debug中的表示 实验11编写子程序 第12章内中断 12.1内中断的产生 12.2中断处理程序 12.3中断向量表 12.4中断过程 12.5中断处理程序和iret指令 12.6除法错误中断的处理 12.7编程处理0号中断 12.8安装 12.9do0 12.10设置中断向量 12.11单步中断 12.12响应中断的特殊情况 实验12编写0号中断的处理程序 第13章int指令 13.1int指令 13.2编写供应用程序调用的 中断例程 13.3对int、iret和栈的深入理解 13.4BIOS和DOS所提供的 中断例程 13.5BIOS和DOS中断例程的 安装过程 13.6BIOS中断例程应用 13.7DOS中断例程应用 实验13编写、应用中断例程 第14章端口 14.1端口的读写 14.2CMOS RAM芯片 14.3shl和shr指令 14.4CMOS RAM中存储的时间信息 实验14访问CMOS RAM 第15章外中断 15.1接口芯片和端口 15.2外中断信息 15.3PC机键盘的处理过程 15.4编写int 9中断例程 15.5安装新的int 9中断例程 实验15安装新的int 9中断例程 第16章直接定址表 16.1描述了单元长度的标号 16.2在其他段中使用数据标号 16_3直接定址表 16.4程序入口地址的直接定址表 实验16编写包含多个功能子程序的 中断例程 第17章使用BIOS进行键盘输入 和磁盘读写 17.1int 9中断例程对键盘输入的处理 17.2使用int 16h中断例程读取 键盘缓冲区 17.3字符串的输入 17.4应用int 13h中断例程对磁盘 进行读写 实验17编写包含多个功能子程序的 中断例程 课程设计2 综合研究 研究试验1搭建一个精简的C语言 开发环境 研究试验2使用寄存器 研究试验3使用内存空间 研究试验4不用main函数编程 研究试验5函数如何接收不定数量的 参数 附注 附注1Intel系列微处理器的3种工作 模式 附注2补码 附注3汇编编译器(masm.exe)对jmp的 相关处理 附注4用栈传递参数 附注5公式证明
第1章 基础知识 1.1 机器语言 1.2 汇编语言的产生 1.3 汇编语言的组成 1.4 存储器 1.5 指令和数据 1.6 存储单元 1.7 CPU对存储器的读写 1.8 地址总线 1.9 数据总线 1.10 控制总线 1.11 内存地址空间(概述) 1.12 主板 1.13 接口卡 1.14 各类存储器芯片 1.15 内存地址空间 第2章 寄存器(CPU工作原理) 2.1 通用寄存器 2.2 字在寄存器中的存储 2.3 几条汇编指令 2.4 物理地址 2.5 16位结构的CPU 2.6 8086CPU给出物理地址的方法 2.7 “段地址×16+偏移地址=物理地址”的本质含义 2.8 段的概念 2.9 段寄存器 2.10 CS和IP 2.11 修改CS、IP的指令 2.12 代码段 实验1 查看CPU和内存,用机器指令和汇编指令编程 第3章 寄存器(内存访问) 3.1 内存中字的存储 3.2 DS和[address] 3.3 字的传送 3.4 mov、add、sub指令 3.5 数据段 3.6 栈 3.7 CPU提供的栈机制 3.8 栈顶超界的问题 3.9 push、pop指令 3.10 栈段 实验2 用机器指令和汇编指令编程 第4章 第1个程序 4.1 一个源程序从写出到执行的过程 4.2 源程序 4.3 编辑源程序 4.4 编译 4.5 连接 4.6 以简化的方式进行编译和连接 4.7 1.exe的执行 4.8 可执行文件中的程序装入内存并运行的原理 4.9 程序执行过程的跟踪 实验3 编程、编译、连接、跟踪 第5章 [bx]和loop指令 5.1 [bx] 5.2 Loop指令 5.3 在Debug中跟踪用loop指令实现的循环程序 5.4 Debug和汇编编译器Masm对指令的不同处理 5.5 loop和[bx]的联合应用 5.6 段前缀 5.7 一段安全的空间 5.8 段前缀的使用 实验4 [bx]和loop的使用 第6章 包含多个段的程序 6.1 在代码段中使用数据 6.2 在代码段中使用栈 6.3 将数据、代码、栈放入不同的段 实验5 编写、调试具有多个段的程序 第7章 更灵活的定位内存地址的方法 7.1 and和or指令 7.2 关于ASCII码 7.3 以字符形式给出的数据 7.4 大小写转换的问题 7.5 [bx+idata] 7.6 用[bx+idata]的方式进行数组的处理 7.7 SI和DI 7.8 [bx+si]和[bx+di] 7.9 [bx+si+idata]和[bx+di+idata] 7.10 不同的寻址方式的灵活应用 实验6 实践课程中的程序 第8章 数据处理的两个基本问题 8.1 bx、si、di、bp 8.2 机器指令处理的数据所在位置 8.3 汇编语言中数据位置的表达 8.4 寻址方式 8.5 指令要处理的数据有多长? 8.6 寻址方式的综合应用 8.7 div指令 8.8 伪指令dd 8.9 dup 实验7 寻址方式在结构化数据访问中的应用 第9章 转移指令的原理 9.1 操作符offset 9.2 jmp 指令 9.3 依据位移进行转移的jmp指令 9.4 转移的目的地址在指令中的jmp指令 …… 第10章 call和ret指令 第11章 标志寄存器 第12章 内中断 第13章 int指令 第14章 端口 第15章 外中断 第16章 直接定址表 第17章 使用BIOS进行键盘输入和磁盘读写 附注

5,823

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 王瑞MVP
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧