现在的位置: 首页 > 综合 > 正文

用汇编实现数字转化为字符串的函数itoa

2013年02月24日 ⁄ 综合 ⁄ 共 2977字 ⁄ 字号 评论关闭

对于熟悉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博客之星的候选人之一,如果你觉得我写的博客还可以,欢迎投上你宝贵的一票,谢谢大家的支持!我的投票地址为:

抱歉!评论已关闭.