对于熟悉C语言的大家来说说,itoa这个函数大家一定不会陌生。itoa是广泛应用的非标准C语言扩展函数,它的功能是:将任意类型的数字转换为字符串。
为了更加清楚地让我们知道,如何使用汇编语言来实现这个函数,下面先以用C语言自己实现一个itoa函数,再来说明使用汇编语言实现方法及思想。因为无论是用C语言还是使用汇编语言,其实现思想和方法都是一样的,只是描述的语言不同。但是我们都比较熟悉C语言,而对汇编语言并不是那么的熟悉,所以为了让我们更加好地理解这个函数的汇编语言实现,我借助C语言的力量来类比说明一下。
注:在本文中出现的数字,“xxx”表示数字对应的字符串,‘x'表示单个数字对应的字符,x表示数字。
一、为什么要把数字转化成字符串
在汇编语言中,数字是不能直接输出的,要想把数字输出,就要将其转换成字符串(即字符)的形式,然后再以输出字符的形式来输出字符串。这个就是我要用汇编实现这个函数的最初目的,就是输出数字。当然,把数字转化成字符串还有很的实际的用途。
二、C语言的实现版本
如何把一个数字转化成一个字符串,即数字转化成字符串的算法,在C语言中,相信大家都很熟悉。就是把数字一直除以10直到商为0,把余数加上‘0’的ASCII码,即可得到余数的ASCII码,即数字对应的字符。例如:数字123,转化成字符串,其字符产生的次序就为‘3’,‘2’,‘1’。最后,1除以10,商为0,余数为1。
可以看到生成的字符的顺序与真正的字符串的顺序是相反的,生成的字符应为“123”。因为32位的数字,最大也不过是10位数,所以我们只要申请一个11个单元的数组就肯定能放得下转换后的字符串,然而我们在转化的时候,并不知道数字转换成字符串后,字符串的长度为多少,也就是说,我们并不知道一开始产生的字符‘3’要放在数组的哪个地方,例如这里的123的话,‘3’应该放在下标为2的单元中,而如果数字是1243的话,‘3’应该放在下标为3的单元中。
由于上面所说的两个原因(产生的字符的顺序与生成的字符串的字符的顺序相反、不能确定生成第一个字符所在的单元位置),并考察栈的数据结构特征,在实现的函数中,我们需要一个栈。把生成的字符入栈,生成结束后,再把栈中的字符出栈,按顺序放到字符数组中,即可完成我们的功能。因为栈是先进后出的,所以‘3’会最后出栈,这样的话,产生的字符串顺序就与预想中的一样。
itoa函数生成的字符串是C风格的字符串,即字符串是以‘\0’结束的,所以我们生成的字符串也应是以字符‘\0’结束。也就是说,‘\0’才是字符串的最后一个字符。所以我们可以把‘\0’先入栈,则它就会在最后出栈,放到字符数组的最后。同时,它还能起到一个标记的作用,也就是说,如果‘\0’出栈,则说明所有产生的字符都已经放到字符数组中,栈为空。
下面来看看它的实现代码:
注:为了与itoa区别,这里使用函数名,dtoc,意思为DoubleWordToChar,要放入的字符串地址由参数str给出。
void wtoc(int num, char *str) { int rem = 0;//余数 char c = '\0';//数字对应的字符 Stack s; //定义一个栈 Init(&s); Push(&s, '\0');//把'\0'压入栈 while(num != 0)//判断商是否为0 { rem = num % 10; //除以10取余数 c = rem + '0'; //把余数转化成对应的字符 Push(&s, c); //把字符压入栈 num /= 10; //求num除以10的商 } do { c = Pop(&s); //字符出栈 *str = c; //顺序地放入到字符数组中 ++str; }while(c != '\0'); //'\0'出栈,所有的字符都复制完结 Destory(&s); }
这段代码的做法与前面所说的实现思想完全相同,不再重复。Stack是一个我们自己定义的数据结构——栈,由于要与汇编中的操作相对应,这里只有两种操作,一个是Push,一个是Pop,除此之外,不提供任何操作。
三、汇编语言的实现
下面再来看看用汇编语言实现的对应的子程序,如下:
;子程序名:dtoc ;功能:将dword型数据转变为十进制数的字符串,字符串以0为结尾 ;参数: (ax)=dword型数据的低16位,(dx)=dword型数据的高16位 ; ds:si指向字符串的地址 ;返回:无 dtoc: push si push cx mov cx, 0 ;把0先压入栈底 push cx rem: ;求余,把对应的数字转换成ASCII码 mov cx, 10 ;设置除数 call divdw ;执行安全的除法 add cx, 30H ;把余数转换成ASCII码 push cx ;把对应的ASCII码压入栈中 or ax, dx ;判断商是否为0 mov cx, ax jcxz copy ;商为0,表示除完 jmp rem ;否则,继续相除 copy: ;把栈中的数据复制到string中 pop cx ;ASCII码出栈 mov [si], cl;把字符保存到string中 jcxz dtoc_return ;若0出栈,则退出复制 inc si ;指向下一个写入位置 jmp copy ;若0没出栈,则继续出栈复制数据 dtoc_return:;恢复寄存器内容,并退出子程序 pop cx pop si ret
汇编子程序说明:
1、我们可以看到汇编程序并不比上面的C语言程序复杂多少,这个我们应该值得高兴。
两个程序中,我都用空行把它分割为5部分,汇编语言子程序的第2、3、4部分,分别对应C语言程序中的第2、3、4部分。在上面的子程序中,我们使用了系统提供给我们的栈,而没有自己去定义一个,因为数字的个数并不多,最多也只有10个,所以我们可以放心地使用系统提供给我们的栈。
2、为了使操作统一,并让除法不产生溢出而影响我们的结果,统一使用上一篇博客——编写无溢出除法的汇编子程序,中的divdw子程序来进行除法,而不直接使用div指令,若要转化的数字只有16位,可在高位置0,使其成为32们的dword类型的数字。由于上一篇博客有详细的说明,这里就不再说明了,它实现的是被除数为32位,除数为16位的无溢出除法。它的功能使用说明如下:
子程序名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型
除数为word型,结果为dword型
参数: (ax)=dword型数据的低16位
(dx)=dword型数据的高16位
(cx)=除数
返回: (dx)=结果的高16位,(ax)=结果的低16位
(cx)=余数
3、标号为rem到到指令jmp rem之间标记的内容相当到C语言实现中的第一个循环,即while循环,标号copy到指令jmp copy之间的内容相当到C语言中的第二个循环,即do while循环。
4、add cx, 30H,表示把cx中的余数(数字)转变成字符,因为,‘0’的ASCII码为30H。
5、mov cx, 0
push cx ;把0先压入栈底
实现就是一开始就把字符'\0'压入栈中,用于在字符串的最后面加入'\0',并用作所有字符都已经出栈的标记。
PS:本人有幸成为了CSDN博客之星的候选人之一,如果你觉得我写的博客还可以,欢迎投上你宝贵的一票,谢谢大家的支持!我的投票地址为:
作者:ljianhui 发表于2013-12-28 1:34:52 原文链接
阅读:153 评论:0 查看评论