《汇编语言:基于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:DWORD
	mov ecx, count				        ;数组大小
	mov esi, arrayPtr				    ;数组指针
	mov edi, 0					
	
L1:	
	mov eax, [esi+edi*4]
	mul intVal						    ;乘以同一个整数
	mov [esi+edi*4], eax
	inc edi								;下一个数
	loop L1

	ret
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:DWORD
	local counter:DWORD
	local position: dword
	mov counter, 0
	mov esi, arrayPtr					;数组的起始地址
	mov edi, arrayPtr
	add edi, TYPE DWORD					;第2个元素   用以相临元素的比较是否自增
	mov ecx, count					    ;数组大小
	mov edx, 0							;设置计数器
	
L1:	
	mov eax,  [esi]
	mov ebx,  [edi]
	cmp ebx, eax						;比较相临两个元素是否递增
	jl L2								;ebx < eax   有符号比较:小于跳转L2
	inc edx								;否则ebx > eax, 递增序列的个数自增
	jmp L3

L2:										;此处EDX中的当前计数器与counter中先前保存的值进行了比较
	mov eax, counter
	cmp edx, eax
	jb L3								;无符号比较:小于跳转L3,如果EDX中的当前值大于之前保存在 counter中的值,则EDX将保存在计数器中
	mov counter, edx					;保存最长递增序列的长度
	mov position, esi					;保存最长序列最后一个成员的索引
	mov edx, 0
L3:
	add esi, TYPE DWORD				    ;下一个元素
	add edi, TYPE DWORD
	loop L1

	mov eax, counter
	inc eax
	ret
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:DWORD
	mov ecx, count							;数组大小
	mov esi, arrayPtr2						;数组指针
	mov edi, arrayPtr3
	mov edx, arrayPtr1
	mov ebx, 0

L1:	
	mov eax, [esi + ebx*4]
	add eax,  [edi + ebx*4]					;相加
	mov [edx + ebx*4], eax
	inc ebx									;下一个数
	loop L1

	ret
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:DWORD
	mov eax, intVal
	cmp eax, 1
	je isNot
	cmp eax, 2					    ;2是最小的素数
	je stop
	mov ecx, intVal
	sub ecx, 2						;1和本身略过
	mov ebx, intVal
	dec ebx
	
L1:	
	mov edx, 0
	mov eax, intVal
	div ebx							;商在eax中,余数在edx中
	cmp edx, 0
	je isNot
	cmp ecx, 1
	je stop
	dec ebx
	loop L1
stop:
	mov eax, 1
	ret
isNot:
	mov eax, 0
	ret
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]);
		else
			printf("%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 = -1

	mov eax, srchVal				;搜索数值
	mov ecx, count				    ;数组大小
	mov esi, arrayPtr				;数组指针
	mov edi, count					;索引到最后
	dec edi							;最后一个元素的索引(数组从0开始)

L1:	cmp [esi+edi*4], eax
	je found
	dec edi							;从后往前索引
	loop L1

notFound:
	mov eax, NOT_FOUND
	jmp short exit

found:
	mov eax, edi

exit:
	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;
}

运行调试:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值