汇编宏与结构体编程实践

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

table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}

190、创建一个宏,该宏等待按键输入并返回按下的键。该宏应包含用于存储 ASCII 码和键盘扫描码的参数。编写一个程序来测试这个宏。

以下是实现该功能的宏和测试程序示例。

宏定义部分:

“`mReadkey MACRO ascii, scan

push ax

call ReadChar

mov ascii, al

mov scan, ah

pop ax

ENDM



### 测试程序部分:
```INCLUDE Irvine32.inc
INCLUDE Macros.inc

.data
ascii BYTE ?
scan  BYTE ?

.code
main PROC
    mReadkey ascii, scan
    movzx eax, ascii
    call WriteDec
    call Crlf
    movzx eax, scan
    call WriteDec
    call Crlf
    exit
main ENDP
END main

上述代码中,首先定义了

mReadkey

宏,它会调用

ReadChar

函数获取按键的 ASCII 码和扫描码,并分别存储到传入的参数中。测试程序调用该宏获取按键信息,并将 ASCII 码和扫描码输出显示。

191、模拟一个人在随机游走过程中,在随机选择的某个时间间隔丢失手机的过程。使用至少一个结构体(STRUCT 指令)和一个或多个条件汇编指令来实现程序。


首先定义一个结构体,添加一个字段来标记手机是否丢失。例如:

```asm
DrunkardWalk STRUCT
    path      COORD  WalkMax DUP(<0,0>)
    pathsUsed WORD   0
    phoneLost BYTE   0 ; 0 表示未丢失,1 表示已丢失
DrunkardWalk ENDS

在随机游走的过程中,利用条件汇编指令来随机决定在某个时间间隔让手机丢失。如在

TakeDrunkenWalk

过程中,在每次人移动后,使用随机数生成器生成一个随机值,根据这个随机值和预设的概率判断是否让手机丢失。若随机值满足条件,将

phoneLost

字段置为 1,并记录当前位置为手机丢失位置。



##192、一些计算机指令集有带三个操作数的算术指令。在这个编程练习中,要求创建模拟三操作数指令的宏。假设 EAX 保留用于宏操作且不被保留,宏修改的其他寄存器必须被保留,所有参数都是有符号的内存双字。编写模拟以下操作的宏:a. add3 目标操作数, 源操作数 1, 源操作数 2;b. sub3 目标操作数, 源操作数 1, 源操作数 2(目标操作数 = 源操作数 1 - 源操作数 2);c. mul3 目标操作数, 源操作数 1, 源操作数 2;d. div3 目标操作数, 源操作数 1, 源操作数 2(目标操作数 = 源操作数 1 / 源操作数 2)。编写一个程序,通过实现四个包含多个操作的算术表达式来测试这些宏。
可按以下步骤编写宏及测试程序。首先编写宏:

```asm
.MODEL SMALL
.STACK 100H
.DATA
w DWORD 10
x DWORD ?
y DWORD 20
z DWORD 30
.CODE
MAIN PROC
    MOV AX, @DATA
    MOV DS, AX

    ; 定义 add3 宏
    add3 MACRO destination, source1, source2
        MOV EAX, source1
        ADD EAX, source2
        MOV destination, EAX
    ENDM

    ; 定义 sub3 宏
    sub3 MACRO destination, source1, source2
        MOV EAX, source1
        SUB EAX, source2
        MOV destination, EAX
    ENDM

    ; 定义 mul3 宏
    mul3 MACRO destination, source1, source2
        MOV EAX, source1
        IMUL EAX, source2
        MOV destination, EAX
    ENDM

    ; 定义 div3 宏
    div3 MACRO destination, source1, source2
        MOV EAX, source1
        CDQ
        IDIV source2
        MOV destination, EAX
    ENDM

    ; 测试宏的算术表达式
    ; 表达式 1: x = (w + y) * z
    add3 temp, w, y
    mul3 x, temp, z

    ; 表达式 2: x = (w - y) / z
    sub3 temp, w, y
    div3 x, temp, z

    ; 表达式 3: x = (w * y) + z
    mul3 temp, w, y
    add3 x, temp, z

    ; 表达式 4: x = (w / y) - z
    div3 temp, w, y
    sub3 x, temp, z

    MOV AH, 4CH
    INT 21H
MAIN ENDP
END MAIN

上述代码中,定义了

add3


sub3


mul3


div3

四个宏来模拟三操作数指令,然后编写了四个包含多个操作的算术表达式来测试这些宏。

193、哪个 Win32 函数返回标准输入的句柄?

GetStdHandle 函数返回标准输入的句柄。

194、哪个Win32函数可以从键盘读取一串文本并将其放入缓冲区?

ReadConsole函数可以从键盘读取文本输入并将其放入缓冲区。

195、描述 COORD 结构。


COORD 结构使用 `STRUCT` 和 `ENDS` 指令定义,内部有两个字段:
- X 为 WORD 类型,偏移量为 00;
- Y 为 WORD 类型,偏移量为 02。

196、哪个Win32函数可以将文件指针移动到相对于文件开始位置的指定偏移量处?

SetFilePointer

197、哪个Win32函数可以让你更改屏幕缓冲区的尺寸?

SetConsoleScreenBufferSize函数可以让你将屏幕缓冲区大小设置为X列Y行。

198、哪个Win32函数可以让你改变后续文本输出的颜色?

SetConsoleTextAttribute函数可以改变后续文本输出的颜色。

199、哪个Win32函数可以让程序暂停指定的毫秒数?

Win32的Sleep函数可以让当前执行的线程暂停指定的毫秒数。

200、调用CreateWindowEx时,窗口的外观信息是如何传递给该函数的?

在调用

CreateWindowEx

时,窗口的外观信息通过参数传递给该函数。如

CreateWindowEx

函数调用中,

ADDR className

指定窗口类名,

ADDR WindowName

指定窗口名称,

MAIN_WINDOW_STYLE

指定窗口样式,这些参数共同传递了窗口的外观信息。

201、列举两个调用MessageBox函数时可以使用的按钮常量。

IDOK、IDCANCEL

202、调用 MessageBox 函数时可以使用的两个图标常量是什么?

MB_ICONQUESTION、MB_ICONSTOP

203、列举WinMain(启动)过程执行的至少三项任务。

获取当前程序的句柄;

加载程序的图标和鼠标光标;

注册程序的主窗口类并指定处理窗口事件消息的过程;

创建主窗口;

显示并更新主窗口;

开始一个循环来接收和分发消息,直到用户关闭应用程序窗口。

204、描述示例程序中WinProc过程的作用。

WinProc过程接收并处理与窗口相关的所有事件消息。主要工作是对每条消息进行解码,若消息被识别,则执行与该消息相关的面向应用程序的任务。

205、在示例程序中,WinProc 过程处理哪些消息?

在示例程序中,

WinProc

过程处理的消息有:

WM_LBUTTONDOWN

(用户按下鼠标左键时生成)

WM_CREATE

(表示主窗口刚刚创建)

WM_CLOSE

(表示应用程序的主窗口即将关闭)

206、描述示例程序中 ErrorHandler 过程的作用。

ErrorHandler 过程为可选过程,在程序主窗口注册和创建期间系统报告错误时被调用。它有几个重要任务:

调用

GetLastError

获取系统错误编号

调用

FormatMessage

获取系统格式化的错误消息字符串

调用

MessageBox

显示包含错误消息字符串的弹出消息框

调用

LocalFree

释放错误消息字符串使用的内存

207、调用CreateWindow后立即激活的消息框是在应用程序主窗口之前还是之后出现?

消息框在应用程序主窗口之后出现。程序先创建主窗口,保存窗口句柄、显示并绘制窗口,之后才显示消息框。

208、由WM_CLOSE激活的消息框是在主窗口关闭之前还是之后出现?

在主窗口关闭之前出现

209、描述线性地址。

线性地址是一个范围在

0


FFFFFFFh

之间的 32 位整数,它指向一个内存位置。

若禁用了分页功能,线性地址就是目标数据的物理地址。

它由段值和变量偏移量组合而成,操作系统可通过分页功能进一步将其转换为物理地址。

210、分页与线性内存有什么关系?

当程序尝试访问线性地址空间中的某个地址时,处理器会自动将线性地址转换为物理地址,此转换称为

页面转换

。若请求的页面当前不在内存中,处理器会中断程序并发出

页面错误

,操作系统会将所需页面从磁盘复制到内存。从应用程序角度看,页面错误和页面转换会自动发生。

211、分页提供了什么优势?

分页是x86处理器的重要特性,它使计算机能够运行原本无法全部装入内存的多个程序组合。处理器通过先仅将程序的一部分加载到内存,其余部分保留在磁盘来实现这一点。

212、哪个寄存器包含局部描述符表的基地址?

LDTR寄存器包含程序的局部描述符表(LDT)的地址。

213、哪个寄存器包含全局描述符表的基地址?

GDTR(全局描述符表寄存器)

214、可以存在多少个全局描述符表?

只能存在一个全局描述符表。

215、可以存在多少个局部描述符表?

一个或多个

216、请列举段描述符中至少四个字段。

基地址、特权级别、段类型、段存在标志、粒度标志

217、分页过程涉及哪些结构?

分页过程中,操作系统维护的页目录和一组页表参与其中,用于跟踪当前内存中所有程序使用的页面。

218、哪个结构包含页表的基地址?

页目录项包含页表的基地址。线性地址中的10位目录字段是页目录项的索引,该页目录项包含页表的基地址。

219、给出一个调用CreateFile函数以打开现有文件进行读取的示例。


INVOKE CreateFile, ADDR filename,      ; ptr to filename
                 GENERIC_READ,         ; read from the file
                 DO_NOT_SHARE,         ; share mode
                 NULL,                 ; ptr to security attributes
                 OPEN_EXISTING,        ; open an existing file
                 FILE_ATTRIBUTE_NORMAL,; normal file attribute
                 0                     ; not used

220、给出一个调用CreateFile函数的示例,该函数将创建一个具有普通属性的新文件,并覆盖任何同名的现有文件。


INVOKE CreateFile, ADDR filename, GENERIC_WRITE, DO_NOT_SHARE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0

221、编写链接库中用于清屏的 Clrscr 过程的自定义版本。

以下是一个类似的

Clrscr

过程示例代码:


;------------------------------------------------------
Clrscr PROC
; 清屏(视频页面 0)并将光标定位在第 0 行,第 0 列。
    pusha
    mov ah, 06h      ; 滚动窗口
    mov al, 0        ; 滚动全部屏幕
    mov bh, 07h      ; 正常属性
    mov ch, 0        ; 左上角行号
    mov cl, 0        ; 左上角列号
    mov dh, 24       ; 右下角行号
    mov dl, 79       ; 右下角列号
    int 10h          ; 调用 BIOS 中断

    mov ah, 02h      ; 设置光标位置
    mov bh, 0        ; 视频页面 0
    mov dh, 0        ; 行号
    mov dl, 0        ; 列号
    int 10h          ; 调用 BIOS 中断
    popa
    ret
Clrscr ENDP

此代码通过 BIOS 中断实现清屏并将光标定位到屏幕左上角。

222、使用线条绘制字符在屏幕上绘制一个盒子。提示:使用WriteConsoleOutputCharacter函数。

要使用

WriteConsoleOutputCharacter

函数在屏幕上用线条绘制字符绘制一个盒子,可按以下步骤操作:

确定盒子的位置和大小,即盒子左上角和右下角在屏幕缓冲区中的坐标。

准备组成盒子的线条绘制字符,如水平和垂直线条字符。

调用

WriteConsoleOutputCharacter

函数,将准备好的线条绘制字符复制到屏幕缓冲区中对应的位置,以形成盒子的形状。

需要注意,Win32 API 函数不会保留

EAX


EBX


ECX


EDX

寄存器的值,因此在调用函数前后需要自行对这些寄存器进行压栈和出栈操作。

223、编写一个程序,使用Win32的ReadConsole函数从用户那里输入以下信息:名字、姓氏、年龄和电话号码。然后使用Win32的WriteConsole函数,以带有标签和美观的格式重新显示相同的信息。不要使用Irvine32库中的任何过程。

以下是实现该功能的基本步骤和示例代码思路,但代码需要在汇编环境中运行。

步骤:

获取标准输入和输出句柄。

定义存储用户输入信息的缓冲区。

使用

ReadConsole

函数依次获取名字、姓氏、年龄和电话号码。

使用

WriteConsole

函数以美观格式显示这些信息。

示例代码(MASM语法):


.386
.model flat, stdcall
option casemap:none

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

.data
hInput HANDLE ?
hOutput HANDLE ?
firstName db 20 dup(0)
lastName db 20 dup(0)
age db 4 dup(0)
phoneNumber db 15 dup(0)
prompt1 db '请输入名字: ', 0
prompt2 db '请输入姓氏: ', 0
prompt3 db '请输入年龄: ', 0
prompt4 db '请输入电话号码: ', 0
outputFormat db '名字: %s 姓氏: %s 年龄: %s 电话号码: %s', 0
newLine db 0dh, 0ah, 0

.code
main proc
    ; 获取标准输入和输出句柄
    invoke GetStdHandle, STD_INPUT_HANDLE
    mov hInput, eax
    invoke GetStdHandle, STD_OUTPUT_HANDLE
    mov hOutput, eax

    ; 提示输入名字
    invoke WriteConsole, hOutput, addr prompt1, sizeof prompt1 - 1, addr bytesWritten, NULL
    invoke ReadConsole, hInput, addr firstName, sizeof firstName - 1, addr bytesRead, NULL

    ; 提示输入姓氏
    invoke WriteConsole, hOutput, addr prompt2, sizeof prompt2 - 1, addr bytesWritten, NULL
    invoke ReadConsole, hInput, addr lastName, sizeof lastName - 1, addr bytesRead, NULL

    ; 提示输入年龄
    invoke WriteConsole, hOutput, addr prompt3, sizeof prompt3 - 1, addr bytesWritten, NULL
    invoke ReadConsole, hInput, addr age, sizeof age - 1, addr bytesRead, NULL

    ; 提示输入电话号码
    invoke WriteConsole, hOutput, addr prompt4, sizeof prompt4 - 1, addr bytesWritten, NULL
    invoke ReadConsole, hInput, addr phoneNumber, sizeof phoneNumber - 1, addr bytesRead, NULL

    ; 输出信息
    invoke WriteConsole, hOutput, addr outputFormat, sizeof outputFormat - 1, addr bytesWritten, NULL
    invoke WriteConsole, hOutput, addr newLine, sizeof newLine - 1, addr bytesWritten, NULL

    invoke ExitProcess, 0
main endp
end main

注意:

上述代码只是示例,需要根据实际汇编环境进行调整和完善。

224、编写一个程序,创建一个新的文本文件。提示用户输入学生的学号、姓氏、名字和出生日期。将这些信息写入文件。以同样的方式输入更多记录,然后关闭文件。

可按以下步骤编写程序:

创建新的文本文件;

提示用户输入学生的学号、姓氏、名字和出生日期;

将输入的信息写入文件;

重复步骤 2 和 3 以输入更多记录;

关闭文件。

不同编程语言实现代码不同,例如在 Python 中实现代码如下:


# 打开文件
file = open('students.txt', 'w')

while True:
    # 提示用户输入信息
    student_id = input('请输入学生学号(输入 q 退出):')
    if student_id == 'q':
        break
    last_name = input('请输入学生姓氏:')
    first_name = input('请输入学生名字:')
    birth_date = input('请输入学生出生日期:')
    # 写入信息到文件
    file.write(f'{student_id},{last_name},{first_name},{birth_date}
')

# 关闭文件
file.close()

225、编写一个汇编程序,使其能够读取比输入缓冲区大的文件。将缓冲区大小减小到1024字节。使用循环继续读取和显示文件,直到无法读取更多数据。如果计划使用WriteString显示缓冲区,请记得在缓冲区数据末尾插入一个空字节。

以下是修改后的ReadFile.asm程序示例,以满足能够读取比输入缓冲区大的文件的需求,同时将缓冲区大小减小到1024字节,并使用循环继续读取和显示文件直到无法读取更多数据,且在使用WriteString显示缓冲区时在末尾插入空字节:


; Reading a File (ReadFile.asm)
; Opens, reads, and displays a text file using
; procedures from Irvine32.lib.

INCLUDE Irvine32.inc
INCLUDE macros.inc

BUFFER_SIZE = 1024 ; 减小缓冲区大小到1024字节

.data
buffer    BYTE BUFFER_SIZE DUP(?)
filename  BYTE 80 DUP(0)
fileHandle HANDLE ?
bytesRead DWORD ? ; 用于存储每次读取的字节数

.code
main PROC
; Let user input a filename.
    mWrite "Enter an input filename: "
    mov edx,OFFSET filename
    mov ecx,SIZEOF filename
    call ReadString

; Open the file for input.
    mov al,0 ; 打开文件用于读取
    mov edx,OFFSET filename
    call OpenInputFile
    mov fileHandle,eax ; 保存文件句柄
    cmp eax,INVALID_HANDLE_VALUE ; 检查文件是否成功打开
    je quit ; 如果打开失败,退出程序

read_loop:
    mov edx,OFFSET buffer ; 缓冲区地址
    mov ecx,BUFFER_SIZE ; 缓冲区大小
    mov eax,fileHandle ; 文件句柄
    call ReadFromFile ; 从文件读取数据
    mov bytesRead,eax ; 保存读取的字节数
    cmp eax,0 ; 检查是否读取到数据
    je close_file ; 如果没有读取到数据,关闭文件

; 在缓冲区末尾插入空字节
    mov al,0
    mov buffer[eax],al

; 显示缓冲区内容
    mov edx,OFFSET buffer
    call WriteString

    jmp read_loop ; 继续读取

close_file:
    mov eax,fileHandle ; 文件句柄
    call CloseFile ; 关闭文件

quit:
    exit
main ENDP

END main

这个程序首先让用户输入文件名,然后尝试打开该文件。如果文件成功打开,程序进入一个循环,不断从文件中读取数据到缓冲区,直到无法读取更多数据。每次读取后,在缓冲区末尾插入一个空字节,然后使用WriteString显示缓冲区内容。最后,关闭文件并退出程序。

226、高级任务:使用动态内存分配函数实现一个单链表。每个链表节点是一个名为 Node 的结构体,该结构体包含一个整数值和一个指向下一个链表节点的指针。使用循环,提示用户输入任意数量的整数。每输入一个整数,就分配一个 Node 对象,将该整数插入到 Node 中,并将该 Node 追加到链表末尾。当输入值为 0 时,停止循环。最后,从头到尾显示整个链表。只有在你之前用高级语言创建过链表时,才应尝试这个项目。

要实现该单链表,需完成以下步骤:

首先定义

Node

结构体,包含一个整数值和指向下一个节点的指针。

接着,在循环中提示用户输入整数,输入 0 则停止。

每次输入非零整数时,使用动态内存分配函数创建新的

Node

对象,将输入的整数存入该对象的数据部分,并将其追加到链表末尾。

最后,从头到尾遍历链表并显示每个节点的数据。

以下是一个简单的 Python 示例代码:


class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

head = None
current = None

while True:
    num = int(input('请输入一个整数(输入 0 停止):'))
    if num == 0:
        break
    new_node = Node(num)
    if head is None:
        head = new_node
        current = new_node
    else:
        current.next = new_node
        current = new_node

print('链表元素如下:')
current = head
while current:
    print(current.value)
    current = current.next

上述代码实现了单链表的创建和显示功能。

227、给定二进制浮点值1101.01101,如何将其表示为十进制分数的和?

二进制数 1101.01101 按位权展开

首先,将二进制数

1101.01101

按位权展开。

位权说明


整数部分

(从右到左):

位权依次是 $2^0$、$2^1$、$2^2$、$2^3$ 等;


小数部分

(从左到右):

位权依次是 $2^{-1}$、$2^{-2}$、$2^{-3}$、$2^{-4}$ 等。

计算过程

$$

egin{align

}

1101.01101 &= 1×2^3 + 1×2^2 + 0×2^1 + 1×2^0 + 0×2^{-1} + 1×2^{-2} + 1×2^{-3} + 0×2^{-4} + 1×2^{-5}

&= 8 + 4 + 0 + 1 + 0 + frac{1}{4} + frac{1}{8} + 0 + frac{1}{32}

&= 13 + frac{8}{32} + frac{4}{32} + frac{1}{32}

&= 13 frac{13}{32}

end{align

}

$$

结论

因此,

1101.01101

可表示为

13 $frac{13}{32}$

,即

13 与 $frac{13}{32}$ 的和

© 版权声明

相关文章

暂无评论

none
暂无评论...