本篇记录《汇编语言:基于X86处理器》第3章 复习题和练习,编程练习的学习。
3.9复习题和练习
3.9.1 简答题
1.举例说明三种不同的指令助记符。
答:MOV,ADD和MUL。
2.什么是调用规范?如何在汇编语言声明中使用它?
答: 在汇编语言中,**调用规范(Calling Convention)** 是定义函数调用过程中参数传递、堆栈管理、寄存器使用和返回值传递等细节的底层规则集。 例如
; 声明内存模型和调用规范(如 stdcall)
.model flat, stdcall ; 32 位平坦内存模型 + stdcall 规范:ml-citation{ref="1" data="citationList"}
3.如何在程序中为堆栈预留空间?
答:.stack 4096
4.说明为什么术语汇编器语言不太正确。
答: 术语"汇编器语言"(assembler language)不太正确,因为它混淆了"汇编语言"(assembly language)与"汇编器"(assembler)工具的区别。
5.说明大端序和小端序之间的区别,并在网络上查找这些术语的起源。
答:大端序和小端序之间的区别是,大小端低位放在高地址,而小端序低位放低地址。
6.为什么在代码中使用符号常量而不是整数常量?
答:使用符号常量会让程序更加容易阅读和维护。设想,如果COUNT在整个程序中出现多次,那么在之后的时间里,程序员就能方便地重新定义它的值。
7.源文件与列表文件的区别是什么?
答:列表文件包含了程序源代码的副本,再加上行号、偏移地址、翻译的机器代码和符号表,适合打印。
8.数据标号与代码标号的区别是什么?
答:数据标号标记数据位置,代码标号标记代码的位置,并且以(:)号结束。
9.(真/假):标识符不能以数字开头。
答:真。第一个字符必须为字母(A...Z, a...z),下划线(_)、@ 、?或$。其后的字符也可以是数字。
10.(真/假);十六进制常量可以写为0x3A。
答:假。汇编代码里不能写成0x开头的十六进制数,这应该写成3Ah.
11.(真/假):汇编语言伪指令在运行时执行。
答:假。汇编的伪指令在汇编时处理。
12.(真/假):汇编语言伪指令可以写为大写字母和小写字母的任意组合。
答:真
13.说出汇编语言指令的四个基本组成部分。
答:1.标号(可选),2.指令助记符(必需),3.操作数(通常是必需的),4.注释(可选)
14.(真/假):MOV是指令助记符的例子。
答:真
15.(真/假):代码标号后面要跟冒号(:),而数据标号则没有。
答:真
16.给出块注释的例子。
答:COMMENT !
....这里是多行注释
CoMMENT !
17.使用数字地址编写指令来访问变量,为什么不是一个好主意?
答:使用数字地址编写指令来访问变量有这几个问题:可维护性差,可读性低,平台依赖性强,安全性风险高,调试困难。
18.必须向ExitProcess过程传递什么类型的参数?
答:DWORD
19.什么伪指令用来结束子程序?
答:ENDP伪指令用来结束子程序。
20.32位模式下,END伪指令中的标识符有什么用?
答:END伪指令标记一个程序的结束。
21.PROTO伪指令的作用是什么?
答:PROTO
伪指令主要用于声明函数原型,为后续的函数调用提供参数和返回值类型规范
22(真/假):目标文件由链接器生成。
答:假,目标文件由汇编器读取源文件生成。
23.(真/假):列表文件由汇编器生成。
答:真。
24.(真/假):链接库只有在生成可执行文件之前才加到程序中。
答:假。
- 静态链接库:在生成可执行文件之前(编译的链接阶段)被嵌入到程序中,成为可执行文件的一部分,程序可独立运行。
- 动态链接库:在程序运行时(如加载或调用时)才被链接和加载到内存中,而非在生成可执行文件之前添加。动态链接库允许内存共享和按需加载,避免了静态链接的冗余拷贝问题
25.哪个数据伪指令定义32位有符号整数变量?
答:SDWORD,例如:val SDWORD ?
26.哪个数据伪指令定义16位有符号整数变量?
答:WORD,例如:val WORD ?
27.哪个数据伪指令定义64位无符号整数变量?
答:SQWORD,例如:val SQWORD ?
28.哪个数据伪指令定义8位有符号整数变量?
答:QWORD,例如:val QWORD ?
29.哪个数据伪指令定义10字节压缩BCD变量?
答:TBYTE, 例如:val TBYTE ?
3.9.2 算法基础
答:val1 EQU 25
val2 EQU 0001 1001b
val3 EQU 31o
val4 EQU 19h
2.通过实验和错误,找出一个程序是否能有多个代码段和数据段。
答:通过实现,一个程序可以有多个代码段和数据段,例如:
;3.9.2_2.asm 第3章 3.9.2算法基础 第2题 验证
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
sum DWORD 0
.data
res SDWORD 0
.code
start PROC
mov bx, 7
add bx, 8
start ENDP
.code
main PROC
mov eax, 5
add eax, 6
mov sum, eax
INVOKE ExitProcess, 0
main ENDP
END main
3.编写数据定义,把一个双字按大端序存放在内存中。
;3.9.2_3.asm 第3章 3.9.2算法基础 第3题 编写数据定义,把一个双字按大端序存放在内存中。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
;big_endian_value db 0xA1, 0xB2, 0xC3, 0xD4
LittleEndianDWORD DWORD 12345678h
.code
main PROC
mov eax, dword ptr [LittleEndianDWORD] ; 加载原始值
mov ebx, 0 ; 初始化目标值
mov edx, eax ; 提取最低有效字节
shl edx, 24 ; 移动到最高有效字节位置
or ebx, edx ; 合并到目标值 此时bx=78000000h
mov edx, 0
mov dl, byte ptr [LittleEndianDWORD+1] ;edx=00000056h
shl edx,16 ;edx=005600h
or ebx,edx ;ebx=78560000h
mov edx, 0
mov dl, byte ptr [littleEndianDWORD+2] ;edx=00000034h
shl edx, 8 ;edx=00003400h
or ebx, edx ;ebx=78563400h
mov edx, 0
mov dl, byte ptr [littleEndianDWORD+3] ;edx=00000012h
or ebx, edx ;ebx=78563412h
mov dword ptr [littleEndianDWORD], ebx ; 将结果存储到内存地址 littleEndianDWORD
mov eax, dword ptr [LittleEndianDWORD] ; 查看修改后的值
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:调试前:

调试后:

内存中的数据由12345678h转换成78563412h
4.试发现用 DWORD 类型定义一个变量时,是否能向其赋予负数值。这说明了汇编器类型检查的什么问题?
;3.9.2_4.asm 第3章 3.9.2 算法基础 第4题 试发现用 DWORD 类型定义一个变量时,是否能向其赋予负数值。这说明了汇编器类型检查的什么问题?
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
sum DWORD -3 ;初始化为一个负数
.code
main PROC
mov eax, 5
add eax, 6
mov sum, eax
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:

说明汇编器检查数据是,如果是负数会转成补码。
5.编写一个程序,包含两条指令:(1)EAX寄存器加5;(2)EDX寄存器加5。生成列表文件并检查由汇编器生成的机器代码。发现这两条指令的不同之处了吗?如果有,是什么?
;3.9.2_5.asm 第3章 3.9.2 算法基础 第5题 编写一个程序,包含两条指令:(1)EAX寄存器加5;(2)EDX寄存器加5。
;生成列表文件并检查由汇编器生成的机器代码。发现这两条指令的不同之处了吗?如果有,是什么?
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
sum DWORD 0
sum2 DWORD 0
.code
main PROC
mov eax, 0
add eax, 5
mov sum, eax
mov edx, 0
add edx, 5
mov sum2, edx
INVOKE ExitProcess, 0
main ENDP
END main
列表文件
Microsoft (R) Macro Assembler Version 14.41.34120.0 06/20/25 16:23:54
3.9.2_5.asm Page 1 - 1
;3.9.2_5.asm 第3章 3.9.2 算法基础 第5题 编写一个程序,包含两条指令:(1)EAX寄存器加5;(2)EDX寄存器加5。
;生成列表文件并检查由汇编器生成的机器代码。发现这两条指令的不同之处了吗?如果有,是什么?
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
00000000 .data
00000000 00000000 sum DWORD 0
00000004 00000000 sum2 DWORD 0
00000000 .code
00000000 main PROC
00000000 B8 00000000 mov eax, 0
00000005 83 C0 05 add eax, 5
00000008 A3 00000000 R mov sum, eax
0000000D BA 00000000 mov edx, 0
00000012 83 C2 05 add edx, 5
00000015 89 15 00000004 R mov sum2, edx
INVOKE ExitProcess, 0
0000001B 6A 00 * push +000000000h
0000001D E8 00000000 E * call ExitProcess
00000022 main ENDP
END main
Microsoft (R) Macro Assembler Version 14.41.34120.0 06/20/25 16:23:54
3.9.2_5.asm Symbols 2 - 1
Segments and Groups:
N a m e Size Length Align Combine Class
FLAT . . . . . . . . . . . . . . GROUP
STACK . . . . . . . . . . . . . 32 Bit 00001000 DWord Stack 'STACK'
_DATA . . . . . . . . . . . . . 32 Bit 00000008 DWord Public 'DATA'
_TEXT . . . . . . . . . . . . . 32 Bit 00000022 DWord Public 'CODE'
Procedures, parameters, and locals:
N a m e Type Value Attr
ExitProcess . . . . . . . . . . P Near 00000000 FLAT Length= 00000000 External STDCALL
main . . . . . . . . . . . . . . P Near 00000000 _TEXT Length= 00000022 Public STDCALL
Symbols:
N a m e Type Value Attr
@CodeSize . . . . . . . . . . . Number 00000000h
@DataSize . . . . . . . . . . . Number 00000000h
@Interface . . . . . . . . . . . Number 00000003h
@Model . . . . . . . . . . . . . Number 00000007h
@code . . . . . . . . . . . . . Text _TEXT
@data . . . . . . . . . . . . . Text FLAT
@fardata? . . . . . . . . . . . Text FLAT
@fardata . . . . . . . . . . . . Text FLAT
@stack . . . . . . . . . . . . . Text FLAT
sum2 . . . . . . . . . . . . . . DWord 00000004 _DATA
sum . . . . . . . . . . . . . . DWord 00000000 _DATA
0 Warnings
0 Errors
同样是加法操作,生成的机器码不一样

6.假设有数值456789ABh,按小端序列出其字节内容。
答:小端序是低位存放在低地址,因此列出字节的内容为:0A8h, 89h, 67h, 45h.
7.声明一个数组,其中包含 120个未初始化无符号双字数值。
答:val sdword 120 dup(?)
8.声明一个字节数组,并将其初始化为字母表的前5个字母。
答:myArray byte 'a', 'b', 'c', 'd', 'e'
9.声明一个 32 位有符号整数变量,并初始化为尽可能小的十进制负数。(提示:参阅第1 章的整数范围。
答:minNegative32 DWORD -2147483648 ; 声明并初始化为最小的 32 位有符号整数
10.声明一个 16 位无符号整数变量 wArray,使其具有 3 个初始值。
答:wArray SWORD 10, 20, 30
11.声明一个字符串变量,包含你最喜欢颜色的名字,并将其初始化为空字节结束的字符串。
答:myColor BYTE "blue", 0
12.声明一个未初始化数组dArray,包含50个有符号双字。
答:myArray DWORD 50 dup(?)
13.声明一个字符串变量,包含单词"TEST"并重复500次。
答:myString BYTE 500 dup("TEST")
14.声明一个数组 bArray,包含 20个无符号字节,并将其所有元素都初始化为 0。
答:bArray SBYTE 20 dup(0)
15.写出下述双字变量在内存中的字节序列(从最低字节到最高字节);
val1 DWORD 87654321h
答:x86处理器在内存中按小端序存放数据,因此从最低字节到最高字节的序列为:21h, 43h, 65h, 87h。
3.10 编程练习
*1.整数表达式的计算
参考 3.2 节的程序 AddTwo,编写程序,利用寄存器计算表达式:A-(A+B)-(C-D)。整数值分配给寄存器 EAX、EBX、ECX 和EDX。
;3.10_1.asm 第3章 编程练习 *1.整数表达式的计算
;参考 3.2 节的程序 AddTwo,编写程序,利用寄存器计算表达式:A-(A+B)-(C-D)。
;整数值分配给寄存器 EAX、EBX、ECX 和EDX。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
sum DWORD 0
.code
main PROC
mov eax, 10
mov ebx, 5
mov ecx, 6
mov edx, 15
add ebx, eax ;ebx = (A + B)
sub eax, ebx ;eax = A - (A + B)
sub ecx, edx ;ecx = C - D
sub eax, ecx ;eax = A - (A + B) - (C - D)
mov sum, eax
INVOKE ExitProcess, 0
main ENDP
END main
结果:10-(10+5)-(6-15)= 4
运行结果:

*2.符号整数常量
编写程序,为一周七天定义符号常量。创建一个数组变量,用这些符号常量作为其初始值。
;3.10_2.asm 第3章 编程练习 *2.符号整数常量
;编写程序,为一周七天定义符号常量。创建一个数组变量,用这些符号常量作为其初始值。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
Monday EQU 1 ;星期一
Tuesday EQU 2 ;星期二
Wednesday EQU 3 ;星期三
Thursday EQU 4 ;星期四
Friday EQU 5 ;星期五
Saturday EQU 6 ;星期六
Sumday EQU 7 ;星期日
WorkdayArray DWORD Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sumday
.code
main PROC
mov eax, [WorkdayArray]
mov ebx, [WorkdayArray+4] ;一个数占32位,4个字节,如果WorkdayArray定义的是BYTE,这里都是加1
mov ecx, [WorkdayArray+8]
mov edx, [WorkdayArray+12]
INVOKE ExitProcess, 0
main ENDP
END main
运行测试:

**3.数据定义
编写程序,对 3.4 节表 3-2 中列出的每一个数据类型进行定义,并将每个变量都初始化为与其类型一致的数值。
;3.10_3.asm 第3章 数据定义
;编写程序,对 3.4 节表 3-2 中列出的每一个数据类型进行定义,
;并将每个变量都初始化为与其类型一致的数值。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
val1 BYTE 0 ;最小的无符号数,8位无符号整数,B代表字节
val2 SBYTE -128 ;最小的有符号数,8位有符号整数,S代表有符号
val3 WORD 0 ;最小的无符号数,16位无符号整数
val4 SWORD -32768 ;最小的有符号数,16位有符号整数
val5 DWORD 0 ;最小的无符号数,32位无符号整数,D代表双(字)
val6 SDWORD -2147483648 ;最小的有符号数,32位有符号整数,SD代表有符号双(字)
val7 FWORD 1234567890h ;48位整数(保护模式中的远指针)
val8 QWORD 1234567812345678h ;64位整数,Q代表四(字)
val9 TBYTE 11223344556677889900h;80位(10字节)整数,T代表10字节
val10 REAL4 5.6 ;32位(4字节)IEEE短实数
val11 REAL8 3.2E-260 ;64位(8字节)IEEE长实数
val12 REAL10 4.6E+4096 ;80位(10字节)IEEE扩展实数
.code
main PROC
mov eax, val5
mov ebx, val6
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:

*4.符号文本常量
编写程序,定义几个字符串文本(引号之间的字符)的符号名称,并将每个符号名称都用于变量定义。
;3.10_4.asm 第3章 *4.符号文本常量
;编写程序,定义几个字符串文本(引号之间的字符)的符号名称,并将每个符号名称都用于变量定义。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
constStr1 TEXTEQU <"this is a string",0>
myString1 BYTE constStr1
constStr2 TEXTEQU <"aaaa is a string",0>
myString2 BYTE constStr2
.code
main PROC
mov eax, 5
mov ebx, DWORD PTR [myString2]
INVOKE ExitProcess, 0
main ENDP
END main
运行调试:

*****5.AddTwoSum 的列表文件
生成 AddTwoSum 程序的列表文件,为每条指令的机器代码字节编写说明。某些字节值的含义可能需要猜测。
;3.10_5.asm 第3章 *****5.AddTwoSum 的列表文件
;生成 AddTwoSum 程序的列表文件,为每条指令的机器代码字节编写说明。
;某些字节值的含义可能需要猜测。
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
sum DWORD 0
.code
main PROC
mov eax, 5
add eax, 6
mov sum, eax
INVOKE ExitProcess,0
main ENDP
END main
列表文件:
Microsoft (R) Macro Assembler Version 14.41.34120.0 06/20/25 23:38:06
3.10_5.asm Page 1 - 1
;3.10_5.asm 第3章 *****5.AddTwoSum 的列表文件
;生成 AddTwoSum 程序的列表文件,为每条指令的机器代码字节编写说明。
;某些字节值的含义可能需要猜测。
.386 ; 32位指令集标识符(机器码无直接对应)
.model flat,stdcall ; 内存模式声明(伪指令无机器码)
.stack 4096 ; 栈空间声明(伪指令无机器码)
ExitProcess PROTO,dwExitCode:DWORD ; 函数原型声明(伪指令)
00000000 .data
00000000 00000000 sum DWORD 0 ; 变量定义(机器码: 00 00 00 00)
00000000 .code
00000000 main PROC
00000000 B8 00000005 mov eax, 5 ; B8: MOV指令 opcode, 05 00 00 00: 立即数5(小端序)
00000005 83 C0 06 add eax, 6 ; 83 C0: ADD指令 opcode+modrm, 06: 立即数6
00000008 A3 00000000 R mov sum, eax ; A3: MOV指令 opcode, 00 00 00 00: sum地址(需链接时确定)
INVOKE ExitProcess,0
0000000D 6A 00 * push +000000000h ; 6A: PUSH立即数 opcode
0000000F E8 00000000 E * call ExitProcess ; E8: CALL相对地址(需重定位)
00000014 main ENDP
END main
Microsoft (R) Macro Assembler Version 14.41.34120.0 06/20/25 23:38:06
3.10_5.asm Symbols 2 - 1
Segments and Groups:
N a m e Size Length Align Combine Class
FLAT . . . . . . . . . . . . . . GROUP
STACK . . . . . . . . . . . . . 32 Bit 00001000 DWord Stack 'STACK'
_DATA . . . . . . . . . . . . . 32 Bit 00000004 DWord Public 'DATA'
_TEXT . . . . . . . . . . . . . 32 Bit 00000014 DWord Public 'CODE'
Procedures, parameters, and locals:
N a m e Type Value Attr
ExitProcess . . . . . . . . . . P Near 00000000 FLAT Length= 00000000 External STDCALL
main . . . . . . . . . . . . . . P Near 00000000 _TEXT Length= 00000014 Public STDCALL
Symbols:
N a m e Type Value Attr
@CodeSize . . . . . . . . . . . Number 00000000h
@DataSize . . . . . . . . . . . Number 00000000h
@Interface . . . . . . . . . . . Number 00000003h
@Model . . . . . . . . . . . . . Number 00000007h
@code . . . . . . . . . . . . . Text _TEXT
@data . . . . . . . . . . . . . Text FLAT
@fardata? . . . . . . . . . . . Text FLAT
@fardata . . . . . . . . . . . . Text FLAT
@stack . . . . . . . . . . . . . Text FLAT
sum . . . . . . . . . . . . . . DWord 00000000 _DATA
0 Warnings
0 Errors
调试对比猜想:

***6.AddVariables 程序
修改 AddVariables 程序使其使用 64 位变量。描述汇编器产生的语法错误,并说明为解决这些错误采取的措施。
答:修改相应变量后,编译报错:

根据错误提示,修改相关代码:
去掉这3行
.386
.model flat, stdcall
.stack 4096
把 ExitProcess PROTO, dwExitCode:DWORD 改成 ExitProcess PROTO
把 INVOKE ExitProcess, 0 改成 call ExitProcess
把 END main 改成 END
修改后,完整的代码如下
;3.10_6_AddVariables.asm ***6.AddVariables 程序
;修改 AddVariables 程序使其使用 64 位变量。描述汇编器产生的语法错误,并说明为解决这些错误采取的措施。
ExitProcess PROTO
.data
firstval QWORD 20002000h
secondval QWORD 11111111h
thirdval QWORD 22222222h
sum QWORD 0
.code
main PROC
mov rax, firstval
add rax, secondval
add rax, thirdval
mov sum, rax
call ExitProcess
main ENDP
END
运行调试:
