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