汇编宏与Win32函数应用解析

内容分享2天前发布
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;
}

137、编写一个名为 mWriteAt 的宏,该宏定位光标并将字符串字面量写入控制台窗口。假设存在 mGotoxy 宏用于定位光标位置,mWrite 宏用于写入字符串。

以下是

mWriteAt

宏的实现:


mWriteAt MACRO X, Y, text
    mGotoxy X, Y
    mWrite text
ENDM

此宏接收三个参数:

X


Y

用于定位光标位置,

text

为要写入控制台窗口的字符串字面量。它先调用

mGotoxy

宏定位光标,再调用

mWrite

宏写入字符串。

138、编写一个名为mDumpMemx的宏,它接收一个参数,即变量的名称。已知有一个mLocate宏,该宏接收两个参数xval和yval,用于进行一些位置定位操作(具体实现可参考示例代码中的假设部分)。你的宏必须调用mLocate宏。展示预处理器在调用mDumpMemx宏,传入一个变量名时生成的源代码。同时假设存在一个mDumpMem宏,其功能是接收变量的偏移量、单元数量和单元大小作为参数进行内存转储操作,演示对mDumpMemx宏的调用。

以下是实现

mDumpMemx

宏的代码示例:


; 假设mLocate宏定义如下
mLocate MACRO xval,yval
    IF xval LT 0        ;; xval < 0?
        EXITM           ;; if so, exit
    ENDIF
    IF yval LT 0        ;; yval < 0?
        EXITM           ;; if so, exit
    ENDIF
    mov bx,0            ;; video page 0
    mov ah,2            ;; locate cursor
    mov dh,yval
    mov dl,xval
    int 10h             ;; call the BIOS
ENDM

; mDumpMemx宏定义
mDumpMemx MACRO varName
    ; 假设定位到坐标(10, 10),可根据需要修改
    mLocate 10, 10
    mDumpMem OFFSET varName, LENGTHOF varName, TYPE varName
ENDM

; 数据段定义
.data
array DWORD 1000h,2000h,3000h,4000h

; 代码段调用示例
.code
main PROC
    mDumpMemx array
    exit
main ENDP
END main

当预处理器展开

mDumpMemx array

语句时,生成的源代码大致如下:


; mLocate部分展开
IF 10 LT 0        ;; xval < 0?
    EXITM         ;; if so, exit
ENDIF
IF 10 LT 0        ;; yval < 0?
    EXITM         ;; if so, exit
ENDIF
mov bx,0          ;; video page 0
mov ah,2          ;; locate cursor
mov dh,10
mov dl,10
int 10h           ;; call the BIOS

; mDumpMem部分展开
; 假设mDumpMem宏内部实现,将参数赋值给ESI, ECX, EBX
mov esi, OFFSET array
mov ecx, LENGTHOF array
mov ebx, TYPE array
; 调用DumpMem库过程
call DumpMem

以上代码假设

mDumpMem

宏会将传入的偏移量、单元数量和单元大小分别赋值给ESI、ECX和EBX,并调用

DumpMem

库过程。实际情况可能根据

mDumpMem

宏的具体实现有所不同。

139、给出一个宏参数具有默认参数初始化器的示例。

例如,

mWriteln

宏可以将包含单个空格的字符串作为其默认参数。如果调用时没有参数,它仍会打印一个空格并换行:


mWriteln MACRO text:=<" ">
    mWrite text
    call Crlf
ENDM

140、编写一条使用 IF 指令的语句,检查常量宏参数 Z 的值;如果 Z 小于零,则在汇编期间显示一条消息,表明 Z 无效。

以下是符合要求的语句示例:


MACRO testMacro Z
    IF Z LT 0
        ECHO 'Z 无效,Z 的值小于零'
    ENDIF
ENDM

在上述代码中,定义了一个名为

testMacro

的宏,其中使用

IF

指令检查参数

Z

的值是否小于零,如果是,则使用

ECHO

指令在汇编期间显示提示消息表明

Z

无效。

141、编写一个简短的宏,展示当宏参数嵌入到字面字符串中时 & 运算符的使用。

以下是满足要求的宏示例:


ShowWarning MACRO message
    mWrite "&message"
ENDM

该宏名为

ShowWarning

,接收一个文本参数

message

,使用

&

运算符将参数嵌入到字面字符串中,并将其传递给

mWrite

宏。

142、当以下语句展开 mLocate 宏时,显示预处理器生成的源代码:.data row BYTE 15 col BYTE 60.code mLocate -2,20 mLocate 10,20 mLocate col,row。假设 mLocate 宏定义为:当传入的第一个参数 xval < 0 时,执行 EXITM 退出;否则生成代码 mov bx,0 mov ah,2 mov dh,第二个参数 mov dl,第一个参数 int 10h


对于 mLocate -2,20:由于 xval = -2 < 0,根据宏定义会执行 EXITM 退出,不生成后续代码。

对于 mLocate 10,20:

mov bx,0

mov ah,2

mov dh,20

mov dl,10

int 10h



对于 mLocate col,row:

mov bx,0

mov ah,2

mov dh,15

mov dl,60

int 10h



##143、模拟醉汉漫步时,程序需要在随机选择的某个时间间隔,让醉汉站在那里时把手机丢掉,如何实现该功能?
要实现此功能,可在模拟醉汉漫步的程序基础上,添加随机时间间隔的生成逻辑,在随机选定的时间点,记录醉汉当前所在位置作为手机丢失的位置。

例如在原有的醉汉漫步程序里,在每次醉汉移动后,生成一个随机数,当该随机数满足特定条件时,认为到达随机选择的时间间隔,此时记录醉汉当前的坐标(使用 `COORD` 结构存储的坐标)作为手机丢失位置,可将该位置信息存储在额外的变量或数据结构中,之后可用于后续的显示等操作。

同时,程序要使用至少一个结构(`STRUCT` 指令)和一个或多个条件汇编指令。

##144、哪个 Win32 函数返回标准输入的句柄?
GetStdHandle 函数返回标准输入的句柄。

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

##146、描述COORD结构体。
```markdown
COORD结构体使用`STRUCT`和`ENDS`指令定义,包含两个字段:
- X(类型为WORD,偏移量为00)
- Y(类型为WORD,偏移量为02)

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

SetFilePointer

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

SetConsoleScreenBufferSize

函数可以改变屏幕缓冲区的尺寸,即将屏幕缓冲区大小设置为 X 列 Y 行。

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

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

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

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

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

调用

CreateWindowEx

函数时,通过参数传递窗口的外观信息。具体为:

第二个参数传递窗口类名的地址(

ADDR className

第三个参数传递窗口名称的地址(

ADDR WindowName

第四个参数传递主窗口样式(

MAIN_WINDOW_STYLE

这些参数共同决定了窗口的外观信息。

152、说出调用MessageBox函数时可以使用的两个按钮常量。

IDOK、IDCANCEL

153、说出调用MessageBox函数时可以使用的两个图标常量。

MB_ICONSTOP、MB_ICONQUESTION

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

获取当前程序的句柄;

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

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

创建主窗口;

显示并更新主窗口;

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

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

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

在示例程序中,它处理以下三种特定消息:

WM_LBUTTONDOWN

WM_CREATE

WM_CLOSE

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

WM_LBUTTONDOWN

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

WM_CREATE

(表示主窗口刚刚创建)

WM_CLOSE

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

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

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

调用

GetLastError

检索系统错误号;

调用

FormatMessage

检索适当的系统格式化错误消息字符串;

调用

MessageBox

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

调用

LocalFree

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

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

在应用程序主窗口之后出现。因为代码中先创建并显示主窗口(

CreateWindowEx


ShowWindow


UpdateWindow

),之后才调用

MessageBox

显示消息框。

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

在主窗口关闭之前出现

160、描述线性地址。

线性地址是一个范围在

0


FFFFFFFh

之间的 32 位整数,它指向一个内存位置。如果名为分页的功能被禁用,线性地址也可能是目标数据的物理地址。

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

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

页面转换

分页机制使得计算机能够运行原本无法全部装入内存的程序组合,操作系统通过维护

页目录



页表

来跟踪当前内存中所有程序使用的页面。

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

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

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

LDTR寄存器

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

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

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

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

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

一个或多个

167、说出段描述符中至少四个字段。

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

168、哪些结构参与了分页过程?

操作系统维护的页目录和一组页表参与了分页过程。

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

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

170、给出一个调用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

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


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

172、使用字符集中的线条绘制字符在屏幕上画一个框。提示:使用WriteConsoleOutputCharacter函数。

可利用

WriteConsoleOutputCharacter

函数将字符集中用于绘制线条的字符依次复制到控制台屏幕缓冲区的相应位置,通过合理安排字符的位置来构成框的形状。首先确定框的左上角和右下角坐标,然后根据框的大小和形状,计算出需要绘制线条字符的位置,使用该函数将相应字符复制到这些位置。

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

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

定义名为

Node

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

Node

的指针。

使用动态内存分配函数(如 C 语言中的

malloc

)来创建

Node

对象。

进入循环,提示用户输入整数。

每次输入整数后,为新的

Node

对象分配内存,将输入的整数存储在该

Node

的整数字段中。

将新的

Node

追加到链表末尾。

若输入为 0,则退出循环。

遍历链表,从头到尾显示每个

Node

中的整数值。

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

首先,将二进制数

1101.01101

拆分为整数部分和小数部分。


整数部分


1101

转换为十进制是

13


小数部分


0.01101

,从左到右每一位对应的十进制分数分别为

1/4


1/8


1/32

所以,

1101.01101

可表示为:


13 + 1/4 + 1/8 + 1/32

175、为什么十进制数0.2不能用有限位数的二进制位精确表示?

在将十进制小数转换为二进制小数时,通常采用

乘2取整

的方法。

对于十进制数

0.2

,将其不断乘以 2:

0.2 × 2 = 0.4

,整数部分为

0

0.4 × 2 = 0.8

,整数部分为

0

0.8 × 2 = 1.6

,整数部分为

1

0.6 × 2 = 1.2

,整数部分为

1

0.2 × 2 = 0.4

,此时又出现了之前的结果,之后的计算会不断循环

这意味着在进行转换时,商的位序列开始重复(如

0011...

),所以无法找到精确的商,即

0.2 不能用有限位数的二进制位表示

176、给定二进制值11011.01011,其规范化后的值是多少?

将二进制点左移4位,使单个“1”出现在二进制点左侧,得到规范化值为

1.101101011 × 2⁴

177、给定二进制值 0000100111101.1,其规范化后的值是多少?

将二进制点左移 4 位,使单个“1”出现在二进制点左侧,得到规范化值为


1.001111011 × 2⁴

178、NaN有哪两种类型?

NaN有两种类型:


安静型NaN

(quiet NaN)


信令型NaN

(signaling NaN)


安静型NaN

可以在大多数算术运算中传播而不引发异常。


信令型NaN

可用于生成浮点无效操作异常。

179、FLD指令允许的最大数据类型是什么,它包含多少位?

最大数据类型是REAL10,包含80位。

180、FSTP指令与FST指令有何不同?


`FST`(存储浮点值)指令将浮点操作数从FPU栈顶复制到内存,**不弹出栈**;

`FSTP`(存储浮点值并弹出)指令先执行与`FST`相同的操作,即把`ST(0)`的值复制到内存,然后将`ST(0)`从栈中**弹出**。

181、FADD指令可以使用哪些类型的操作数?

FADD指令可使用的操作数类型有:

单精度浮点变量(如

mySingle

内存地址指向的双精度浮点值(如

REAL8 PTR [esi]

32位浮点内存操作数(

m32fp

64位浮点内存操作数(

m64fp

ST(0)


ST(i)

寄存器操作数

182、FISUB指令与FSUB指令有何不同?

以下是将给定文本内容调整为Markdown格式的输出:


FISUB指令会将源操作数转换为双扩展精度浮点格式,再从`ST(0)`中减去该操作数;而`FSUB`指令中,当使用内存操作数时,是直接从`ST(0)`中减去内存操作数,且不弹出栈,`FSUBP`指令在执行减法操作后会弹出栈顶元素。

183、哪条指令将整数操作数加载到ST(0)中?

FILD指令将16位、32位或64位有符号整数源操作数转换为双精度浮点数并加载到ST(0)中。

184、FPU控制字中的哪个字段可以让你更改处理器的舍入模式?

RC字段

185、将分数5/8转换为二进制实数。

0.101

186、将分数17/32转换为二进制实数。

首先,将分子和分母分别转换为二进制:

分子17转换为二进制:

用除2取余的方法,17÷2 = 8余1,8÷2 = 4余0,4÷2 = 2余0,2÷2 = 1余0,1÷2 = 0余1。从下往上取余数,得到17的二进制表示为10001。

分母32转换为二进制:

因为32 = 2⁵,所以32的二进制表示为100000。

然后,进行二进制长除法:

用10001除以100000。

10001÷100000 = 0.10001(二进制)。

所以,分数17/32转换为二进制实数是

0.10001

187、写出一个包含两条指令的序列,将浮点运算单元(FPU)状态标志移到EFLAGS寄存器中。

使用FNSTSW指令将FPU状态字移到AX,再用SAHF指令将AH复制到EFLAGS寄存器。

188、给定精确结果 1.010101101,使用 FPU 的默认舍入方法将其舍入为 8 位有效数字。

FPU 的默认舍入方法是舍入到最接近的偶数。精确结果

1.010101101

舍入到 8 位有效数字是

1.0101011

189、编写实现以下 C++ 代码的汇编指令:double B = 7.8; double M = 3.6; double N = 7.1; double P = -M * (N + B);


.data
B   REAL8   7.8
M   REAL8   3.6
N   REAL8   7.1
P   REAL8   ?
.code
fld N       ; 将 N 加载到 ST(0)
fadd B      ; 执行 N + B
fld M       ; 将 M 加载到 ST(0)
fchs        ; 对 M 取负
fmul        ; 执行 -M * (N + B)
fstp P      ; 将结果存储到 P

190、编写一段汇编代码来实现以下 C++ 代码的功能:int B = 7; double N = 7.1; double P = sqrt(N) + B;


.data
B SDWORD 7
N REAL8 7.1
P REAL8 ?

.code
fld B       ; 将整数 B 加载到 ST(0)
fld N       ; 将实数 N 加载到 ST(0)
fsqrt       ; 对 ST(0) 中的 N 取平方根
fadd        ; 将 ST(0) 中的 sqrt(N) 与 ST(1) 中的 B 相加
fstp P      ; 将 ST(0) 中的结果存储到 P

191、手动汇编以下指令,并为每个带标签的指令写出十六进制机器语言字节。假设 val1 位于偏移量 0 处。使用 16 位值时,字节必须按小端序排列:mov dx,OFFSET val1 ; d. mov dl,2 ; e. mov bx,1000h ; f.

对于

mov dx,OFFSET val1

,因为

val1

偏移量为 0,小端序存储 16 位立即数,指令编码为

B2 00

(假设此处编码逻辑类似示例中 MOV 指令编码);

对于

mov dl,2

,编码为

B2 02

对于

mov bx,1000h

,小端序存储 16 位立即数,编码为

B7 00 10

所以结果依次为:

B2 00



B2 02



B7 00 10

192、编写一个程序,提示用户输入圆的半径。计算并显示圆的面积。假设存在ReadFloat和WriteFloat过程用于读取浮点数输入和输出浮点数结果,使用FLDPI指令将π加载到寄存器栈中。

以下是实现该功能的程序思路和代码示例:

思路:

使用

ReadFloat

过程获取用户输入的圆的半径。

使用

FLDPI

指令将 π 加载到寄存器栈。

计算圆的面积(面积 = π * 半径 * 半径)。

使用

WriteFloat

过程显示计算得到的圆的面积。

代码示例(假设使用汇编语言):


.data
radius  REAL8 ?
area    REAL8 ?

.code
; 提示用户输入半径
call ReadFloat

; 将用户输入的半径保存到 radius 变量
fstp radius

; 加载 π 到寄存器栈
fldpi

; 加载半径到寄存器栈
fld radius

; 计算半径的平方
fmul radius

; 计算面积(π * 半径 * 半径)
fmul

; 将计算得到的面积保存到 area 变量
fstp area

; 显示面积
fld area
call WriteFloat

193、编写一个程序,将两个或更多的值压入FPU栈,通过调用ShowFPUStack显示栈内容,显示每个FPU数据寄存器的标记值,并显示对应ST(0)的寄存器编号。(对于后者,调用FSTSW指令将状态字保存到一个16位整数变量中,并从第11位到第13位提取栈顶指示器。)使用以下示例输出作为参考:—— FPU栈 —— ST(0): +1.5000000E+000 ST(1): +2.0000000E+000 R0为空 R1为空 R2为空 R3为空 R4为空 R5为空 R6有效 R7有效 ST(0) = R6

由于无法直接编写代码,以下是实现该程序的思路步骤:

初始化程序环境。

将两个或更多值压入FPU栈。

调用

ShowFPUStack

函数显示FPU栈内容。

读取并解析标记寄存器,显示每个FPU数据寄存器的标记值。

调用

FSTSW

指令将FPU状态字保存到16位整数变量。

从该变量的第11 – 13位提取栈顶指示器,确定对应

ST(0)

的寄存器编号并显示。

不同编程语言和环境实现代码不同,如在汇编语言中可按上述步骤用对应指令编写。

© 版权声明

相关文章

暂无评论

none
暂无评论...