汇编编程实践:数组与字符串处理

内容分享3小时前发布
0 0 0

102、创建一个名为 FindThrees 的过程,如果数组中某处有三个连续的 3,则返回 1;否则返回 0。该过程的输入参数列表包含数组的指针和数组的大小。声明过程时使用带参数列表的 PROC 指令。保留过程修改的所有寄存器(EAX 除外)。编写一个测试程序,用不同的数组多次调用 FindThrees。

以下是符合要求的汇编代码示例:


INCLUDE Irvine32.inc

; 声明 FindThrees 过程的原型
FindThrees PROTO, pArray:PTR DWORD, arraySize:DWORD

.data
; 测试数组 1
array1 DWORD 1, 2, 3, 3, 3, 4, 5
size1 DWORD ($ - array1) / TYPE DWORD

; 测试数组 2
array2 DWORD 1, 2, 3, 4, 5
size2 DWORD ($ - array2) / TYPE DWORD

; 测试数组 3
array3 DWORD 3, 3, 3, 6, 7
size3 DWORD ($ - array3) / TYPE DWORD

.code
main PROC
    ; 调用 FindThrees 测试数组 1
    INVOKE FindThrees, ADDR array1, size1
    mov ebx, eax
    call WriteInt
    call Crlf

    ; 调用 FindThrees 测试数组 2
    INVOKE FindThrees, ADDR array2, size2
    mov ebx, eax
    call WriteInt
    call Crlf

    ; 调用 FindThrees 测试数组 3
    INVOKE FindThrees, ADDR array3, size3
    mov ebx, eax
    call WriteInt
    call Crlf

    exit
main ENDP

; FindThrees 过程的实现
FindThrees PROC USES esi ecx, pArray:PTR DWORD, arraySize:DWORD
    mov esi, pArray
    mov ecx, arraySize
    sub ecx, 2 ; 因为要检查连续三个元素,所以循环次数减 2

L1:
    mov eax, [esi]
    cmp eax, 3
    jne Next

    mov eax, [esi + 4]
    cmp eax, 3
    jne Next

    mov eax, [esi + 8]
    cmp eax, 3
    jne Next

    mov eax, 1 ; 找到连续三个 3,返回 1
    jmp Done

Next:
    add esi, 4 ; 移动到下一个元素
    loop L1

    mov eax, 0 ; 未找到连续三个 3,返回 0

Done:
    ret
FindThrees ENDP

END main

上述代码中,首先声明了

FindThrees

过程的原型。在

main

过程中,定义了三个测试数组,并多次调用

FindThrees

过程进行测试。

FindThrees

过程通过循环遍历数组,检查是否存在连续三个 3 的情况,根据结果返回 1 或 0。

103、编写一个名为ShowParams的过程,该过程显示调用它的过程的运行时栈上32位参数的地址和十六进制值。参数应按从最低地址到最高地址的顺序显示。该过程的输入是一个整数,用于指示要显示的参数数量。例如,假设main中的以下语句调用MySample并传递三个参数:INVOKE MySample, 1234h, 5000h, 6543h。然后,在MySample内部,你应该能够调用ShowParams并传递要显示的参数数量:MySample PROC first:DWORD, second:DWORD, third:DWORD paramCount = 3 call ShowParams, paramCount ShowParams应按以下格式显示输出:Stack parameters: ————————— Address 0012FF80 = 00001234 Address 0012FF84 = 00005000 Address 0012FF88 = 00006543

以下是实现

ShowParams

过程的示例代码:


.386
.model flat, stdcall
option casemap:none

include masm32includewindows.inc
include masm32includekernel32.inc
includelib masm32libkernel32.lib

ShowParams PROC paramCount:DWORD
    pushad                ; 保存所有通用寄存器
    mov ecx, paramCount   ; 将参数数量存入ECX
    mov esi, esp          ; 获取当前栈指针
    add esi, 4            ; 跳过返回地址

    ; 显示标题
    mov edx, offset title
    call StdOut
    mov edx, offset line
    call StdOut

L1:
    ; 显示地址
    mov edx, offset address_msg
    call StdOut
    mov eax, esi
    call IntToHex
    mov edx, offset equal_msg
    call StdOut

    ; 显示值
    mov eax, [esi]
    call IntToHex
    call Crlf

    add esi, 4            ; 指向下一个参数
    loop L1

    popad                 ; 恢复所有通用寄存器
    ret
ShowParams ENDP

.data
title         db 'Stack parameters:', 0
line          db '---------------------------', 0
address_msg   db 'Address ', 0
equal_msg     db ' = ', 0
buffer        db 16 dup(0)

.code
main PROC
    ; 示例调用
    push 6543h
    push 5000h
    push 1234h
    mov eax, 3
    call ShowParams
    invoke ExitProcess, 0
main ENDP

END main

此代码定义了

ShowParams

过程,它接收一个整数参数表示要显示的参数数量。过程会遍历栈上的参数,按要求格式显示每个参数的地址和值。代码中使用了一些辅助过程,如

StdOut

用于输出字符串,

IntToHex

用于将整数转换为十六进制字符串,

Crlf

用于换行。

104、在执行字符串原语指令时,哪个方向标志设置会使索引寄存器在内存中向后移动?

设置方向标志(STD指令)会使ESI和EDI递减,即索引寄存器在内存中从高地址向低地址移动,也就是向后移动。

105、当方向标志位清除且SCASB指令找到匹配字符时,EDI指向哪里?

EDI指向匹配字符之后的一个位置。

106、在字符串处理的Str_trim过程中,为什么使用JNE指令?


Str_trim

过程中,

JNE

指令用于判断当前字符是否为要去除的分隔符。

如果不是分隔符(即比较结果不相等),则跳转到

L2

处插入空字节,

以此来确定字符串的新结束位置。

107、如果一个字符串包含一个数字,某个将字符串转换为大写的Str_ucase过程会发生什么?

数字的ASCII码不在

'a'


'z'

的范围内,当遇到数字时,程序会执行

cmp al,'a'

比较,结果为小于则跳转至 L2,执行

inc esi

指向下一个字符,然后继续循环,不会对数字进行转换操作。

108、当数组包含1024个元素时,二分查找算法所需的最大比较次数是多少?

10

109、给出一个32位模式下基址 – 变址操作数的示例。


.data
array WORD 1000h,2000h,3000h

.code
mov ebx,OFFSET array
mov esi,2
mov ax,[ebx+esi] ; AX = 2000h

mov edi,OFFSET array
mov ecx,4
mov ax,[edi+ecx] ; AX = 3000h

mov ebp,OFFSET array
mov esi,0
mov ax,[ebp+esi] ; AX = 1000h

110、假设一个双字二维数组有三个逻辑行和四个逻辑列。编写一个使用ESI和EDI的表达式来寻址第二行第三列的元素。(行和列的编号从0开始)

下面是一个双字(doubleword)数组中某个元素的地址计算公式:

地址=ESI+EDI×4+16×2+3×4地址=ESI+EDI×4+16×2+3×4

其中:


ESI

指向数组的起始地址;


EDI

为行索引(若不考虑特殊索引情况,其值为0);


16

表示每行的字节数(因为每行包含4个元素,每个元素为4字节,所以 $ 4 imes 4 = 16 $ 字节);


2

表示目标元素所在的行号(即第3行);


3

表示目标元素在该行中的列号(即第4个元素)。

111、编写使用 CMPSW 指令来比较两个名为 sourcew 和 targetw 的 16 位值数组的指令。


.data
sourcew WORD 多个 16 位值
targetw WORD 多个 16 位值

.code
mov esi,OFFSET sourcew
mov edi,OFFSET targetw
cld ; 方向 = 向前
mov ecx,LENGTHOF sourcew ; 重复计数器
repe cmpsw ; 相等时重复比较

112、编写使用 SCASW 指令在名为 wordArray 的数组中扫描 16 位值 0100h,并将匹配成员的偏移量复制到 EAX 寄存器的指令。

以下是实现该功能的代码示例:


.data
wordArray WORD 100h, 0100h, 300h

.code
mov edi, OFFSET wordArray    ; EDI 指向数组
mov ax, 0100h                ; 搜索值 0100h
mov ecx, LENGTHOF wordArray  ; 设置搜索计数
cld                          ; 方向 = 向前
repne scasw                  ; 不相等时重复
jnz quit                     ; 若未找到则退出
sub edi, 2                   ; 找到:回退 EDI 到匹配元素位置
mov eax, edi                 ; 将匹配元素的偏移量复制到 EAX
quit:

113、展示如何调用 Str_trim 过程并从字符串中移除所有尾随的“@”字符。

要调用

Str_trim

过程并移除字符串中所有尾随的“@”字符,可使用如下代码:


.data
string_1 BYTE "your_string_with_@_at_end@", 0
.code
INVOKE Str_trim, ADDR string_1, '@'

其中,

string_1

是你要处理的字符串,在实际使用时需将

"your_string_with_@_at_end@"

替换为实际的字符串。

114、埃拉托斯特尼筛法由同名的希腊数学家发明,它提供了一种快速找出给定范围内所有质数的方法。该算法需要创建一个字节数组,通过以下方式插入 1 来“标记”数组位置:从位置 2(2 是质数)开始,在数组中每个 2 的倍数的位置插入 1。然后对下一个质数 3 的倍数做同样的操作。找到 3 之后的下一个质数 5,并标记所有 5 的倍数的位置。以此类推,直到找出所有质数的倍数。数组中未被标记的位置所对应的数字即为质数。对于这个程序,创建一个包含 65000 个元素的数组,并显示 2 到 65000 之间的所有质数。声明该数组,并使用 STOSB 指令将其填充为 0。

该题要求使用埃拉托斯特尼筛法找出 2 到 65000 之间的所有质数。具体步骤如下:

创建一个 65000 元素的数组。

声明此数组,并使用

STOSB

指令将数组填充为 0。

从位置 2 开始,标记 2 的倍数的数组位置。

接着标记 3 的倍数、5 的倍数等质数的倍数位置。

最后,数组中未被标记的位置对应数字即为质数,需将这些质数显示出来。

115、编写一个冒泡排序程序,添加一个变量作为交换标志。当在内层循环中交换一对值时,将该变量设置为1。如果在一次完整遍历数组的过程中没有发生交换,则使用这个变量提前结束排序。

以下是修改后的冒泡排序程序:


;-------------------------------------------------------
; BubbleSort
; Sort an array of 32-bit signed integers in ascending
; order, using the bubble sort algorithm.
; Receives: pointer to array, array size
; Returns: nothing
;-------------------------------------------------------
BubbleSort PROC USES eax ecx esi edx, pArray:PTR DWORD, ; pointer to array 
             Count:DWORD ; array size

    mov ecx, Count
    dec ecx ; decrement count by 1

L1:
    mov edx, 0 ; 初始化交换标志为0
    push ecx ; save outer loop count
    mov esi, pArray ; point to first value

L2:
    mov eax, [esi]
    cmp [esi + 4], eax
    jg L3 ; if [ESI] <= [ESI + 4], no exchange
    mov edx, 1 ; 设置交换标志为1
    xchg eax, [esi + 4]
    mov [esi], eax

L3:
    add esi, 4 ; move both pointers forward
    loop L2 ; inner loop

    pop ecx ; retrieve outer loop count
    cmp edx, 0 ; 检查交换标志
    je L4 ; 如果没有交换,退出排序
    loop L1 ; else repeat outer loop

L4:
    ret
BubbleSort ENDP

116、生成一个随机的 4×4 字母矩阵,其中每个字母有 50% 的概率是元音字母。遍历矩阵的每一行、每一列和每条对角线,生成字母集合。仅显示恰好包含两个元音字母的四字母集合。

可按以下步骤实现该程序:

生成一个 4×4 字母矩阵,每个字母有 50% 概率为元音字母;

遍历矩阵的行、列和对角线,生成字母集合;

筛选出恰好包含两个元音字母的四字母集合;

显示这些集合。

例如,若生成的矩阵为:


P O A Z
A E A U
G K A E
I A G D

则程序显示的四字母集合为:POAZ、GKAE、IAGD、PAGI、ZUED、PEAD 和 ZAKI。

117、创建一个 Str_trim_leading 过程,该过程可以从字符串中移除所有前置指定字符的实例。例如,若用指向字符串“###ABC”的指针调用它,并传入 # 字符,结果字符串将是“ABC”。

以下是实现此功能的代码:


;------------------------------------------------------------
; Str_trim_leading
; Remove all occurrences of a given delimiter
; character from the beginning of a string.
; Returns: nothing
;------------------------------------------------------------
Str_trim_leading PROC USES eax ecx esi, pString:PTR BYTE, ; points to string
                 char: BYTE                             ; character to remove

    mov esi, pString                     ; 指向字符串起始位置
    INVOKE Str_length, esi               ; 返回字符串长度到 EAX
    cmp eax, 0                           ; 字符串长度是否为零?
    je L3                                ; 是: 现在退出

    mov ecx, eax                         ; 否: ECX = 字符串长度

L1:
    mov al, [esi]                        ; 获取一个字符
    cmp al, char                         ; 它是分隔符吗?
    jne L2                               ; 否: 开始处理
    inc esi                              ; 是: 继续前进
    dec ecx                              ; 减少计数器
    cmp ecx, 0                           ; 是否到达字符串末尾
    je L3                                ; 是: 退出
    jmp L1                               ; 继续循环

L2:
    mov edi, pString                     ; 指向目标字符串起始位置
    mov edx, esi                         ; 指向当前处理位置
    mov ecx, eax                         ; 重置计数器
    sub ecx, esi                         ; 计算剩余字符数
    add ecx, 1                           ; 加上空字符
    cld                                  ; 方向 = 向前
    rep movsb                            ; 复制字符串

L3:
    ret
Str_trim_leading ENDP

这个

Str_trim_leading

过程会移除字符串开头的所有指定分隔符字符。它首先找到第一个不是分隔符的字符,然后将剩余的字符串复制到原字符串的起始位置。

118、STRUCT 指令的用途是什么?


`STRUCT` 指令用于创建用户定义类型的模板或模式。许多结构已在 Windows API 库中定义,用于在应用程序和库之间传输数据。结构可以包含不同类型的字段,每个字段声明可使用字段初始化器为字段分配默认值。结构本身不占用内存,但结构变量会占用内存。

119、假设已定义以下结构:RentalInvoice STRUCT invoiceNum BYTE 5 DUP(’ ‘) dailyPrice WORD ? daysRented WORD ? RentalInvoice ENDS 说明以下每个声明是否有效:a. rentals RentalInvoice <> b. RentalInvoice rentals <> c. march RentalInvoice <‘12345’,10,0> d. RentalInvoice <,10,0> e. current RentalInvoice <,15,0,0>

a. 有效;b. 无效;c. 有效;d. 无效;e. 无效

120、(判断正误):宏不能包含数据定义。

错误

121、哪个指令用于标记条件语句块的结束?

在条件汇编指令中,

ENDIF

用于结束由条件汇编指令开始的块;在条件控制流指令中,

.ENDIF

用于终止跟随

.IF


.ELSE


.ELSEIF

指令的语句块,

.ENDW

用于终止跟随

.WHILE

指令的语句块。

122、宏定义中 & 运算符的作用是什么?

在宏定义里通常没有

&

运算符用于解决对参数名称的模糊引用这种标准用法,该答案与常见宏定义知识不符,所以 answer 为

DELETE

123、在宏定义中,! 运算符的作用是什么?

! 运算符(字面字符运算符)的作用与字面文本运算符类似,它强制预处理器将预定义的运算符视为普通字符。例如在 TEXTEQU 定义中,

!

运算符可防止

>

符号成为文本分隔符,如:


BadYValue TEXTEQU <Warning: Y - coordinate is!> 24>

124、在宏定义中,%运算符的作用是什么?

扩展运算符(

%

)可扩展文本宏,或将常量表达式转换为文本表示形式。它有几种不同的使用方式:


TEXTEQU

一起使用时,

%

运算符计算常量表达式并将结果转换为整数;

如果宏需要常量整数参数,

%

运算符可以灵活地传递整数表达式,该表达式会计算为整数值并传递给宏;

当扩展运算符(

%

)是源代码行的第一个字符时,它指示预处理器扩展同一行上找到的所有文本宏和宏函数。

125、创建一个名为SampleStruct的结构体,包含两个字段:字段1是一个16位的WORD,字段2是一个包含20个32位DWORD的数组。字段的初始值可以不定义。


SampleStruct STRUCT
    field1 WORD?
    field2 DWORD 20 DUP(?)
SampleStruct ENDS

126、编写一条语句,用于检索SYSTEMTIME结构的wHour字段。

movzx eax,sysTime.wHour

127、使用以下Triangle结构,声明一个结构变量并将其顶点初始化为(0,0)、(5, 0)和(7,6):Triangle STRUCT Vertex1 COORD <> Vertex2 COORD <> Vertex3 COORD <> Triangle ENDS

可以使用以下两种方式声明并初始化结构变量:

triangle1 Triangle <<0,0>, <5,0>, <7,6>>

triangle2 Triangle {{0,0}, {5,0}, {7,6}}

128、编写一个名为 mPrintChar 的宏,用于在屏幕上显示单个字符。它应该有两个参数:第一个参数指定要显示的字符,第二个参数指定该字符应重复显示的次数。以下是一个示例调用:mPrintChar ‘X’,20

以下是

mPrintChar

宏的实现:


mPrintChar MACRO char, count
    push ecx
    mov ecx, count
L1:
    push eax
    mov al, char
    call WriteChar
    pop eax
    loop L1
    pop ecx
ENDM

此宏接收两个参数,字符和重复次数。它使用循环来重复显示字符指定的次数。

129、编写一个名为 mGenRandom 的宏,用于生成一个介于 0 到 n – 1 之间的随机整数。让 n 作为唯一的参数。

以下是实现

mGenRandom

宏的代码示例:


mGenRandom MACRO n
  push eax
  mov eax, n
  call RandomRange
  pop eax
ENDM

该宏接收一个参数

n

,通过调用

RandomRange

过程生成

0


n - 1

之间的随机整数。

130、编写一个名为 mPromptInteger 的宏,该宏显示一个提示信息并从用户处输入一个整数。向它传递一个字符串字面量和一个双字变量的名称。示例调用:.data minVal DWORD?.code mPromptInteger “Enter the minimum value”, minVal

以下是

mPromptInteger

宏的实现:


mPromptInteger MACRO prompt, varName
    pushad
    mov edx, OFFSET prompt
    call WriteString
    call ReadInt
    mov varName, eax
    popad
ENDM
© 版权声明

相关文章

暂无评论

none
暂无评论...