汇编语言综合研究试验5--函数如何接收不定数量的参数

这个试验是综合研究阶段最后一个试验,也是学习汇编语言的最后一点内容,至此,我已经将课设1、2,综合研究1-5均写入博客,大家可以参考,如果有疑问,欢迎交流!

一.实验过程

1.编写a.c文件,查看汇编代码在这里插入图片描述
在这里插入图片描述
容易分析,a.c中调用函数是通过栈来传递参数的,调用前将参数从右往左依次入栈。
参数在函数中是局部变量,这种方式和创建局部变量的方式类似,可以认为是在子程序调用前为子程序创建局部变量
所不同的是子程序里局部变量通过保存和恢复sp寄存器来释放局部变量空间,参数的局部变量必须通过调用完成后多次调用pop操作来释放栈空间

2.编写b.c文件
通过上文的分析,c程序在调用函数前,首先对所有参数依次入栈,在调用后依次出栈。
在函数内部,如果知道了有多少个参数,可以根据bp+偏移量来找到参数所在的位置,完成参数的接收
在这里插入图片描述
然而,此程序为不定数量参数,可以看到,在这个程序中,函数声明成了void showchar(int,int,…),参数1传入了一个字符的数量,参数2传入显示的颜色,参数3开始传入要显示的字符。函数通过参数1来控制显示字符的循环次数,通过这种方式来接收多个参数
3.printf函数接收参数的原理
编写一个c.c文件,编译,连接,查看汇编代码
在这里插入图片描述
看到编译器将字符e(65h),d(64h),c(63h),b(62h),a(61h)入栈后,又将0194h入栈后调用的printf子程序,
在这里插入图片描述
由上边的分析,0194h肯定是和printf的格式控制部分相关的一个数据,我们猜想可能是指向这样一个字符串的地址,下面验证这个猜想
在debug下用g 215命令执行到偏移地址为215h的位置,也就是printf调用前。用d ds:0194命令查看0194h处的内存,如图
在这里插入图片描述
我们发现,编译器确实把第一个参数放到了数据段中,并且把偏移地址入栈传入printf函数中,那么,printf函数是如何知道有多少个参数的呢?为了看的更清楚,修改上面的程序
在这里插入图片描述
可以看到,第二次调用时,入栈的地址为01A3h
在这里插入图片描述
在debug下用g 227命令执行到偏移地址为227h的位置,也就是第二个printf调用前。用d ds:01a3命令查看01a3h处的内存,如图
在这里插入图片描述
比较两次printf的调用,我们发现,
0194h指向的字符串的十六进制为:25 63 2c 25 63 2c 25 63 2c 25 63 2c 25 63 00
对应的字符串是:%c,%c,%c,%c,%c
01A3h指向的字符串的十六进制为:25 64 2c 25 64 00
对应的字符串是:%d,%d
通过以上分析,发现字符串后边都有一个0作为结尾。
printf可能是根据传入的%的个数来确定打印的字符数,读入一个%就会读取后面一个字符来确定打印的方式,当读出一个0时打印结束
为了验证这个猜想,我们在debug模式下修改这个字符串,再看调用的结果
a、执行到printf函数调用之前,查看0194h处的内存
b、我们把第二个%c后的‘,’对应的16进制值2c修改为0(也就是我们刚才猜想的字符串结束的标志)。再次运行看是否只打印出两个字符。
c、用debug的e命令修改0199h处的值为0,如图
在这里插入图片描述
这个字节的内存值已经修改成了0,用debug的g命令将程序运行完毕,屏幕上只打印出了两个字符(a,b)
在这里插入图片描述
这说明printf函数确实是通过字符串的%个数来传递打印的字符数的

4.实现一个简易的print函数

void print(int color,int row, int col, char * str, ...);

void main()
{
	print(3,14,30,"%c,%c,%c,%c",'a','b','c','d');
	print(4,15,30,"%d,%d",7,8);
}

/*参数列表:颜色,行,列,打印格式,附加参数1,附加参数2...*/
void print(int color,int row, int col, char * str, ...)
{
	int strnum = 0;	/*字符串位置计数器*/
	int stacknum = 0;	/*栈字符位置计数器*/
	char ch = str[strnum++];			/*要处理的下一个字符*/
	int scrnum = 80*2*row + col*2;	/*显存位置*/
	int quotient = 0;		/*保存每次除法的商*/
	int pushnum = 0;		/*除法时入栈次数计数器*/
	while (ch)		/*如果下一个字符为0,则跳出循环*/
	{
		if (ch == '%')
		{
			/*如果ch是%,那么先读出下一个字符*/
			ch = str[strnum++];
			/*判断下一个字符是c还是d,并分别处理*/
			switch (ch)
			{
				/*如果是c,按照字符型输出栈中相应数据*/
				case 'c':
					*(char far *)(0xb8000000 + (scrnum++)) = *(int *)(_BP + 12 + (stacknum++));
					*(char far *)(0xb8000000 + (scrnum++)) = color;
					break;
					
				/*如果是d,按照十进制整形输出栈中相应的数据*/
				case 'd':
					pushnum = 0;
					quotient = *(int *)(_BP + 12 + (stacknum++));
					if (quotient == 0)
					{
						*(char far *)(0xb8000000 + (scrnum++)) = '0';
						*(char far *)(0xb8000000 + (scrnum++)) = color;
					}
					while(quotient)
					{
						_CX = quotient%10;
						_SP -= 2;			/*模拟入栈过程*/
						*(int *)(_SP) = _CX;
						pushnum++;
						quotient /= 10;
					}
					while(pushnum--)
					{
						_CX = *(int *)(_SP);       /*模拟出栈过程*/ 
						_SP += 2;
						*(char far *)(0xb8000000 + (scrnum++)) = _CL + 48;
						*(char far *)(0xb8000000 + (scrnum++)) = color;
					}
					break;
			}
			stacknum++;
		}
		else 		/*如果当前ch值不是%,那么直接将ch写入到显存*/
		{
			*(char far *)(0xb8000000 + (scrnum++)) = ch;
			*(char far *)(0xb8000000 + (scrnum++)) = color;
		}
		
		/*读取下一个字符*/
		ch = str[strnum++];
	}
}

在这里插入图片描述

二.总结思考

思考:b.exe汇编代码中出现的cwd指令
在这里插入图片描述
CWD是汇编语言中的字扩展指令,它的功能是将一个字型变量扩展为双字型变量。高位在DX,低位在AX。

三.研究体会

通过这个试验的学习,我们明白了,函数如何接收参数,printf函数是如何确定参数个数的,如何传递参数并显示的,理解了这些,printf函数的编写就相对容易些了。

综合研究阶段所需要的tc2.0我已经上传网盘,免费分享给大家,关注我的公众号,菜单栏–>技术相关–>免费资源即可领取,还有网课查答案,包括学习通、智慧树、mooc,内容涵盖计算机、政治等等,还有其他免费资源(简历模板、电子版书籍和考试资料等等),说不准大家会有用(分享干货)!

扫码加关注吧!
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值