用匯編實現數字轉化爲字符串的函數itoa

對於熟悉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博客之星的候選人之一,如果你覺得我寫的博客還可以,歡迎投上你寶貴的一票,謝謝大家的支持!我的投票地址爲:


發佈了126 篇原創文章 · 獲贊 456 · 訪問量 268萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章