《汇编语言:基于X86处理器》第13章 复习题和编程练习
本篇记录了《汇编语言:基于X86处理器》第13章 复习题和编程练习的学习笔记。
13.6 复习题
1.当汇编过程被高级语言程序调用时,主调程序与被调过程是否应使用相同的内存模式?
答:主调程序与被调过程使用的内存模式必须相同。
2.C 和 C++程序调用汇编过程时,为什么区分大小写是很重要的?
答:C和C++编译器编译时是区分大小写的,所以如果调用汇编过程必须区分大小写,这样才能正确调用相应的汇编过程。
3.一种编程语言的调用规范是否包括了过程对某些寄存器的保存规定?
答:是的
4.(是/否):EVEN 和ALIGN伪指令是否都能用于内嵌汇编代码?
答:是
5.(是/否):OFFSET运算符是否能用于内嵌汇编代码?
答:否
6.(是/否):内嵌汇编代码中,DW和DUP运算符是否都能用于变量定义?
答:否
7.使用 fastcall调用规范时,若内嵌汇编代码修改了寄存器会出现什么情况?
答:_fastcall会使用编译器用寄存器来传递参数,会引起寄存器冲突,使用程序会结果错乱。
8.不使用OFFSET运算符,是否还有其他方法能把变量偏移量送入变址寄存器?
答:要以使用LEA指令得到变量偏移地址。例如 :lea esi,buffer ;将buffer的偏移地址送入ESI.
9.对 32位整数数组使用 LENGTH运算符,其返回值是多少?
答:LENGTH
的返回值是数组的元素个数(与每个元素的大小无关 )
10.对长整型数组使用SIZE运算符,其返回值是多少?
答:返回值是元素的个数*单个元素的大小。LENGTH array * TYPE long
11.标准C printf()函数的有效汇编PROTO 声明是怎样的?
答:printf PROTO C, pString:PTR BYTE, args:VARARG
12.调用如下C语言函数,实参x是最先入栈还是最后入栈?
void MySub( x, y, z);
答:X是最后入栈,调用C语言函数时,是从右向左逆向入栈。
13.过程被 C++调用时,其外部声明使用的“C”说明符有什么作用?
答:防止c++的名称修饰。从汇编语言程序员的角度来看,名称修饰存在的问题是:C++编译器让链接器去找的是修饰过的名称,而不是生成可执行文件时的原始名称。
14.C++调用外部汇编过程时,为什么名称修饰是重要的?
答:C++编译器编译代码时会对函数名称进行修饰,例如:sub()函数编译时可能变成了_sub()函数,如果不使用名称修饰就会找不到对应的原始名称。
15.搜索互联网,用简表列出C/C++编译器使用的优化技巧。
答:
1. 高级优化
内联展开(Inline Expansion)
将小函数调用替换为函数体本身,减少调用开销(如-finline-functions
)。
循环展开(Loop Unrolling)
减少循环控制开销,通过重复循环体(如#pragma unroll
或-funroll-loops
)。
常量传播(Constant Propagation)
将常量表达式替换为计算结果(如int x = 3 * 5;
→ int x = 15;
)。
死代码消除(Dead Code Elimination)
删除不可达的代码(如未使用的变量或条件分支)。
函数返回值优化(RVO/NRVO)
避免临时对象的复制(直接构造返回值到目标内存)。
2. 循环优化
循环不变代码外提(Loop Invariant Code Motion)
将循环内不变的表达式移到循环外。
循环融合(Loop Fusion)
合并相邻的循环以减少迭代次数。
循环分块(Loop Tiling)
优化内存访问局部性(尤其对多维数组)。
3. 内存与指针优化
别名分析(Alias Analysis)
推断指针是否指向同一内存区域(如restrict
关键字)。
标量替换(Scalar Replacement)
将数组元素替换为局部变量(减少内存访问)。
写缓冲优化(Write Buffering)
合并多次内存写入操作。
4. 指令级优化
指令调度(Instruction Scheduling)
重新排列指令以避免CPU流水线停顿。
自动向量化(Auto-Vectorization)
使用SIMD指令(如SSE/AVX)并行化计算(-mavx
)。
分支预测优化(Branch Prediction)
通过重排代码提高分支预测命中率(如likely/unlikely
宏)。
5. 链接时优化(LTO, Link-Time Optimization)
跨编译单元优化(如-flto
),允许内联和删除未使用的全局函数。
6. 其他常见优化
尾调用优化(Tail Call Optimization)
将递归尾调用转为循环(避免栈溢出)。
公共子表达式消除(CSE)
重复计算的表达式只计算一次。
强度削减(Strength Reduction)
用低成本操作替换高成本操作(如乘法→加法)。
编译器标志示例(GCC/Clang)
-O1
:基础优化(如常量传播、死代码消除)。
-O2
:激进优化(包括向量化、循环展开)。
-O3
:最高级优化(可能增加代码体积)。
-Os
:优化代码大小。
-Ofast
:激进优化,忽略严格标准合规性。
注意事项
调试与优化冲突:高优化级别可能导致调试信息不准确(如变量被优化掉)。
未定义行为(UB):依赖UB的代码可能被激进优化破坏(如指针越界)。
性能权衡:某些优化(如循环展开)可能增加代码体积,需根据场景选择。
编译器通过组合这些技术,在保证语义一致性的前提下最大化性能。实际效果可通过反汇编(objdump -d
或编译器资源管理器)验证。
13.7编程练习
**1.数组与整数相乘
编写汇编子程序,实现一个双字数组与一个整数的乘法。编写C/C++测试程序,新建数组并将其传递给子程序,再输出运算后的结果数组。
头文件
#pragma once // 防止头文件被重复包含, 非标准(但广泛支持)
//ArrayMul.h 对应的汇编语言文件ArrayMul.asm C++测试文件13.7_1.cpp extern "C" {void ArrayMul(int n, int array[], unsigned count);// Assembly language module
}
汇编语言实现文件
;ArrayMul函数 ArrayMul.asm C++测试文件13.7_1.cpp .586
.model flat, C
ArrayMul PROTO,intVal:DWORD, arrayPtr:PTR DWORD, count:DWORD.code
;数组乘以同一个整数。返回:无
ArrayMul PROC USES ecx esi edi,intVal:DWORD, arrayPtr:PTR DWORD, count:DWORDmov ecx, count ;数组大小mov esi, arrayPtr ;数组指针mov edi, 0 L1: mov eax, [esi+edi*4]mul intVal ;乘以同一个整数mov [esi+edi*4], eaxinc edi ;下一个数loop L1ret
ArrayMul ENDP
END
C++测试文件
//13.7_1.cpp 13.7编译练习 **1.数组与整数相乘
//编写汇编子程序,实现一个双字数组与一个整数的乘法。
//编写C / C++测试程序,新建数组并将其传递给子程序,再输出运算后的结果数组。#include <iostream>
#include <time.h>
#include "ArrayMul.h"
using namespace std;int main()
{const int ARRAY_SIZE = 10;int array[ARRAY_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array[i]);cout << endl;int intVal;cout << "Enter an integer value: ";cin >> intVal;ArrayMul(intVal, array, ARRAY_SIZE);for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array[i]);cout << endl;return 0;
}
运行调试:
***2.最长递增序列
编写汇编子程序,接收两个输人参数:数组偏移量和数组大小。子程序返回数组中最长的递增序列中整数值的个数。比如,数组如下所示,则最长的严格递增序列开始于索引值为3的元素、序列长度为4{14,17、26、42}:
[-5,10,20,14,17,26,42、22,19,-5]
编写 C/C++测试程序调用该子程序,测试程序实现的操作包括:新建数组、传递参数、输出子程序的返回值。
头文件:LongestSelfIncSeq.h
#pragma once // 防止头文件被重复包含, 非标准(但广泛支持)
//LongestSelfIncSeq.h 对应的汇编语言文件LongestSelfIncSeq.asm C++测试文件13.7_2.cpp extern "C" {int LongestSelfIncSeq(int array[], unsigned count);// Assembly language module
}
汇编实现文件: LongestSelfIncSeq.asm
;LongestSelfIncSeq函数 LongestSelfIncSeq.asm C++测试文件13.7_2.cpp .586
.model flat, C
LongestSelfIncSeq PROTO,arrayPtr:PTR DWORD, count:DWORD.code
;查找数组中最长的递增序列中整数值的个数。返回:eax为最长递增序列的个数
LongestSelfIncSeq PROC USES ecx esi edi,arrayPtr:PTR DWORD, count:DWORDlocal counter:DWORDlocal position: dwordmov counter, 0mov esi, arrayPtr ;数组的起始地址mov edi, arrayPtradd edi, TYPE DWORD ;第2个元素 用以相临元素的比较是否自增mov ecx, count ;数组大小mov edx, 0 ;设置计数器L1: mov eax, [esi]mov ebx, [edi]cmp ebx, eax ;比较相临两个元素是否递增jl L2 ;ebx < eax 有符号比较:小于跳转L2inc edx ;否则ebx > eax, 递增序列的个数自增jmp L3L2: ;此处EDX中的当前计数器与counter中先前保存的值进行了比较mov eax, countercmp edx, eaxjb L3 ;无符号比较:小于跳转L3,如果EDX中的当前值大于之前保存在 counter中的值,则EDX将保存在计数器中mov counter, edx ;保存最长递增序列的长度mov position, esi ;保存最长序列最后一个成员的索引mov edx, 0
L3:add esi, TYPE DWORD ;下一个元素add edi, TYPE DWORDloop L1mov eax, counterinc eaxret
LongestSelfIncSeq ENDP
END
C++测试文件:13.7_2.cpp
//13.7_2.cpp 13.7编译练习 ***2.最长递增序列
//编写汇编子程序,接收两个输人参数:数组偏移量和数组大小。子程序返回数组中最长的递增序列中整数值的个数。
//比如,数组如下所示,则最长的严格递增序列开始于索引值为3的元素、序列长度为4{ 14,17、26、42 } :
// [-5,10,20,14,17,26,42、22,19, - 5]
//编写 C / C++测试程序调用该子程序,测试程序实现的操作包括 : 新建数组、传递参数、输出子程序的返回值。#include <iostream>
#include <time.h>
#include "LongestSelfIncSeq.h"using namespace std;int main()
{int array[10] = { -5, 10, 20, 14, 17, 26, 42, 22, 19, -5 };int num = LongestSelfIncSeq(array, sizeof(array) / 4);printf("Reslut is: %d\n", num);return 0;
}
运行调试:
**3.三个数组求和
编写汇编子程序。接收三个同样大小数组的偏移量。将第二个和第三个数组加到第一个数组上。子程序返回时,第一个数组包含结果数值。编写 C/C++测试程序,新建数组并将其传递给子程序,再显示第一个数组的内容。
头文件:ArraySum.h
#pragma once // 防止头文件被重复包含, 非标准(但广泛支持)
//ArraySum.h 对应的汇编语言文件ArraySum.asm C++测试文件13.7_3.cpp extern "C" {void ArraySum(int array1[], int array2[], int array3[], unsigned count);// Assembly language module
}
汇编文件ArraySum.asm
;ArraySum函数 ArraySum.asm C++测试文件13.7_3.cpp .586
.model flat, C
ArraySum PROTO,arrayPtr1:PTR DWORD, arrayPtr1:PTR DWORD, arrayPtr3:PTR DWORD, count:DWORD.code
;把第2个数组与第3个数组相加,和放在第1个数组。返回:无
ArraySum PROC USES ecx esi edi ebx edx,arrayPtr1:PTR DWORD, arrayPtr2:PTR DWORD, arrayPtr3:PTR DWORD, count:DWORDmov ecx, count ;数组大小mov esi, arrayPtr2 ;数组指针mov edi, arrayPtr3mov edx, arrayPtr1mov ebx, 0L1: mov eax, [esi + ebx*4]add eax, [edi + ebx*4] ;相加mov [edx + ebx*4], eaxinc ebx ;下一个数loop L1ret
ArraySum ENDP
END
C++测试文件:
//13.7_3.cpp 13.7编译练习 **3.三个数组求和
//编写汇编子程序。接收三个同样大小数组的偏移量。将第二个和第三个数组加到第一个数组上。
//子程序返回时,第一个数组包含结果数值。编写 C / C++测试程序,新建数组并将其传递给子程序,
//再显示第一个数组的内容。#include <iostream>
#include <time.h>
#include "ArraySum.h"
using namespace std;int main()
{const int ARRAY_SIZE = 10;int array1[ARRAY_SIZE] = { 0 };int array2[ARRAY_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int array3[ARRAY_SIZE] = { 11,12, 13, 14, 15, 16, 17, 18, 19, 20 };cout << "array 2: " << endl;for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array2[i]);cout << endl;cout << "array 3: " << endl;for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array3[i]);cout << endl;cout << "array1 = array2 + array3: " << endl;ArraySum(array1, array2, array3, ARRAY_SIZE);for (unsigned i = 0; i < ARRAY_SIZE; i++)printf("%d\t", array1[i]);cout << endl;cout << endl;return 0;
}
运行调试:
***4.质数程序
编写汇编过程实现如下功能:若传递给EAX 的 32 位整数为质数,则返回1;若 EAX 为非质数,则返回 0。要求从高级语言程序调用该过程。由用户输入一组整数,对每个数值,程序都要显示一条信息以示该数是否为质数。建议:第一次调用该过程时,使用厄拉多塞过滤算法(Sieve ofEratosthenes)初始化布尔数组。
头文件:isPrime.h
#pragma once // 防止头文件被重复包含, 非标准(但广泛支持)
//isPrime.h 对应的汇编语言文件isPrime.asm C++测试文件13.7_4.cpp extern "C" {int isPrime(unsigned intVal);// Assembly language module
}
汇编语言过程实现文件:isPrime.asm
;isPrime函数 isPrime.asm C++测试文件13.7_4.cpp .586
.model flat, C
isPrime PROTO,intVal:DWORD .code
;判断是否为素数。返回:是素数eax为1, 不是素数eax为0
isPrime PROC USES ecx edx,intVal:DWORDmov eax, intValcmp eax, 1je isNotcmp eax, 2 ;2是最小的素数je stopmov ecx, intValsub ecx, 2 ;1和本身略过mov ebx, intValdec ebxL1: mov edx, 0mov eax, intValdiv ebx ;商在eax中,余数在edx中cmp edx, 0je isNotcmp ecx, 1je stopdec ebxloop L1
stop:mov eax, 1ret
isNot:mov eax, 0ret
isPrime ENDP
END
C++测试文件:13.7_4.cpp
//13.7_4.cpp 13.7编译练习 ***4.质数程序
//编写汇编过程实现如下功能:若传递给EAX 的 32 位整数为质数,则返回1; 若 EAX 为非质数,则返回 0。
//要求从高级语言程序调用该过程。由用户输入一组整数,对每个数值,程序都要显示一条信息以示该数是否为质数。
//建议:第一次调用该过程时,使用厄拉多塞过滤算法(Sieve ofEratosthenes)初始化布尔数组。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "isPrime.h"
using namespace std;int main()
{int array[6] = { 0 };printf("Please input six integers:\n");for (unsigned i = 0; i < 6; i++)scanf("%d", &array[i]);printf("\n");for (unsigned i = 0; i < 6; i++){if (isPrime(array[i]))printf("%d is prime\n", array[i]);elseprintf("%d is not prime\n", array[i]);}printf("\n");return 0;
}
运行调试:
*5.LastindexOf过程
修改13.3.1 节的 IndexOf 过程。将新函数命名为LastlndexOf,使其从数组末尾开始反向搜索。遇到第一个匹配值就返回其索引,否则即为未发现匹配值,返回-1。
.h头文件
#pragma once // 防止头文件被重复包含, 非标准(但广泛支持)
// indexof.h 对应的汇编语言文件IndexOf.asm C++测试文件13.7_5.cpp extern "C" {long IndexOf(long n, long array[], unsigned count);// Assembly language module
}
汇编实现文件
;IndexOf函数 IndexOf.asm C++测试文件13.7_5.cpp .586
.model flat, C
IndexOf PROTO,srchVal:DWORD, arrayPtr:PTR DWORD, count:DWORD.code
;---------------------------------------------------------------
IndexOf PROC USES ecx esi edi,srchVal:DWORD, arrayPtr:PTR DWORD, count:DWORD
;
;对32位整数数组执行线性搜索——从后往前搜索
;寻找指定数值。如果发现匹配数值
;用EAX返回该数值的索引位置
;否则,EAX返回-1.
;----------------------------------------------------------------NOT_FOUND = -1mov eax, srchVal ;搜索数值mov ecx, count ;数组大小mov esi, arrayPtr ;数组指针mov edi, count ;索引到最后dec edi ;最后一个元素的索引(数组从0开始)L1: cmp [esi+edi*4], eaxje founddec edi ;从后往前索引loop L1notFound:mov eax, NOT_FOUNDjmp short exitfound:mov eax, ediexit:ret
IndexOf ENDP
END
C++测试文件
//13.7_5.cpp 13.7编译练习 *5.LastindexOf过程
//修改13.3.1 节的 IndexOf 过程。将新函数命名为LastlndexOf,使其从数组末尾开始反向搜索。
//遇到第一个匹配值就返回其索引,否则即为未发现匹配值,返回 - 1。
#include <iostream>
#include <time.h>
#include "indexof.h"
using namespace std;int main(){//用伪随机数填充数组。const unsigned ARRAY_SIZE = 100000;const unsigned LOOP_SIZE = 100000;const char* boolstr[] = { "false","true" };long array[ARRAY_SIZE];for (unsigned i = 0; i < ARRAY_SIZE; i++)array[i] = rand() % 50000;long searchVal;time_t startTime, endTime;cout << "Enter an integer value to find: ";cin >> searchVal;cout << "Please wait...\n";//测试汇编函数time(&startTime);long index = 0;for (unsigned n = 0; n < LOOP_SIZE; n++)index = IndexOf( searchVal, array, ARRAY_SIZE );bool found = index != -1;time(&endTime);cout << "Elapsed ASM time: " << long(endTime - startTime)<< " seconds. Found = " << boolstr[found] << endl;return 0;
}
运行调试: