關鍵字:
二進位 十進位 16進位 進位 組合語言 8086彙編
摘要:"二進位",這個術語是電腦專業中一個相當重要的概念。它是整個現代電腦的基礎。普通的電腦使用者往往很難弄懂二進位運算的來龍去脈。不過即使弄不懂它,也不影響使用電腦。但是對於電腦專業的學生或者程式設計人員,一定要弄懂它,而且能夠編寫2-10進位轉化的程式。本文試圖教會程式設計人員弄懂什麼是2,10,16進位,以及2-10進位轉化的原理和方法。
進位
什麼是進位?自然界的數可能無窮大。而符號的個數是有限的。用有限的符號來表示無窮大的數必然涉及到進位。先說說10進位。
10進位
因為人有10個指頭,而手指頭是最容易計數的工具(我想很多人都見過小學生掰指頭數數的情境),所有最自然的進位是10進位。10進位就需要10個符號,現在世界最通用符號是阿拉伯數字,0,1,2,3,4,5,6,7,8,9. 十以內的數用一個符號來表示,10^1(表示10的1次方,下同)以上且10^2以下的數用2個符號來表示,以此類推。十進位的特點是逢十進一。如果一個數用三個符號來表示,從高位到低位的3個符號依次是d2,d1,d0. 則d2,d1,d0這三個符號表示數:(d2
* 10^2) + (d1 * 10^1) + (d0 * 10^0)。
2進位
10進位,對人類來說,10進位是最直觀標記法。但對電腦來說,2進位才是最方便的表示。2進位僅僅需要2個符號,故2進位的物理表示僅需要2種狀態,這對電子器件來說十分方便的,如高電平和低電平,電路的通與斷,電路也會大大簡化。同時,在邏輯運算中(數學中有一個分支叫布爾代數)也僅僅需要2個值,即真和假,所以使用2進位作為電腦的基礎,不僅可簡化電路設計,也可以很好的處理邏輯運算。邏輯運算包含邏輯加(or),邏輯乘(and),邏輯反(not),異或(xor)等。
各種CPU一般都支援各種算術運算(如加,減,乘,除),邏輯運算以及移位元運算。移位元運算和邏輯運算一般通稱位元運算。2進位只需要2個符號0和1,大於2的數必須使用多個符號來表示,2進位的特點是逢二進一。一般說來,表示2^n以內的整數之多需要n位2進位數來表示。一位2進位數叫1個位元,英文名稱為bit,8位2進位數組合起來叫做1個位元組(byte),可表示0到2^8-1的數。16位2進位數做和起來叫做1個字(word),可表示0到2^16-1(65535)之間的數。32位2進位數叫做1個雙字(dword),可表示0到2^32-1的數。64位2進位叫做一個qword。
我們常常聽到8位機,16位彙編,32位彙編,32位作業系統,32位CPU,64位作業系統這樣的說法。這裡的位就是2進位的位。16位彙編指供16位CPU使用的
組合語言,對應的機器指令是16位機器指令。32位作業系統是指,在這樣的作業系統上,可以運行32位機器指令, 相應的地址空間可達2^32. 由於作業系統和CPU都是向下相容的,故32位作業系統(如windows 95,windows xp)下仍可運行16位的dos程式, 甚至於CPU是是64位的CPU.
一點兒題外話. 現在主流的CPU,作業系統,編譯器都是32bit的(實際上最新的CPU是64位的),竊以為,16位組合語言是過時的,只學習32位組合語言即可. 但教科書往往是過時的,初學者仍然不得不學已經趨於淘汰的16位彙編,編寫16位DOS程式.
數的內部表示和字串表示.
上文提到,1個0-65535之間的數需要2個位元組來表示,1個0-4294967295的數需要4個位元組來表示. 前者可以放入1個16bit的寄存器,如ax,bx,cx,dx,si,di,sp,bp,後者可以放入1個32bit的寄存器,如eax,ebx,ecx,edx,esp,ebp,esi,edi. 由於寄存器名稱本身已經隱含了數的大小(這裡指是16bit格式還是32bit格式),所有通常無需特別的修飾,但如果運算元是記憶體表示,必須使用修飾符,如1個名為n的32bit整數,想要比較它是否等於0,在x86彙編,你需要這樣寫"cmp
dword ptr [n],0",這裡"dword ptr"表示n是一個32bit的數. 數的內部表示是最經濟的標記法,具有佔用空間少且長度固定的優點. 如一個32bit的數總是佔用4位元組. 但這種標記法也有缺點,就是不能直接顯示,必須把它轉化為字串來輸入和輸出. 如數1000000000,它的內部表示僅需4位元組.如果表示為16進位串,它是"3B9ACA00", 這個字串佔用8位元組,如果表示為10進位串,它是"1000000000",這個字串佔用10位元組. 而對於數1,無論是16進位還是10進位,它的字串表示形式總是相同的,僅僅佔用1個位元組.
為什麼需要進位轉化
因為數的內部表示總是緊湊的2進位表示,但在輸入輸出中使用的字串形式是16進位或者10進位的,所有在輸入和輸出時必須作進位轉化. 同時,輸入和輸出必須使用作業系統提供的系統調用或者進階語言提供庫函數來實現. 進階語言提供了各種各樣的庫函數,相當比重的庫函數是和輸入輸出相關的.同時,我們也可以在組合語言和進階語言中使用作業系統提供的系統調用來實現. 例如,如果輸出一個字串在標準輸出裝置. 你可以使用下列方法來做.
1. 在C語言中,調用標準庫函數printf
2. 在16位dos環境, 可使用Dos調用9號功能
3. 在32位windows環境,可使用Windows API 函數MessageBox,輸出字串在新視窗
4. 在32位windows環境,可使用Windows API 函數WriteFile, 第一個參數為標準輸出裝置,輸出字串到控制台.
printf函數除了可輸出字串外,甚至可將一個整數或者浮點數轉化為字串輸出.如printf("%d",n),可將整數轉化為10進位字串並輸出. 所以對於C語言,你可能不需要進位轉化,printf可以代勞。當然,如故你覺得printf的效能不能令你滿意,你也可以編寫一個轉化函數。 但是,對於組合語言,就沒有這麼方便了,許多彙編初學者遇到第一個困難的問題就是如果將整數轉化為字串輸出. 為此,你得首Crowdsourced Security Testing道電腦中的字元是怎樣表示的. 目前,電腦的西文符號最普通的標記法是ASCII,這套符號體系可表示128個符號,每個符號為1位元組,其編碼為0-127,最高bit為0.
這128個符號可表示26個大小寫字元,0-9這10個數字,以及一些符號和不可顯示的控制字元,如斷行符號(16進位形式為0d). 響鈴(16進位形式為07). 數0需要顯示為符號'0'. 如在8086組合語言中,一個0-9之間的數,存放在al寄存器,你需要將其轉化為字串,存在以di開始的記憶體空間,你需要類似這樣的2條彙編指令, "add al, 48"和 "mov byte ptr [di],al".為什麼要加上48呢?因為符號'0'的ASICII碼是48,當然,上面的指令也可以寫為 "add al, '0'"
或"add al, 30h".它們是完全等價的.
轉化1個數為R進位字串
如果一個數小於n,轉化為n進位非常後只有1個字元,非常簡單。上文就是1個例子。本節講述普通的數如何轉化為1個R進位的字串. 1個數n轉化為R進位的
字串表示,採用除r取餘法,重複的計算n % r的餘數和n/r的商,依次得到R進位的各個字元,值得注意的是,得到的R進位的各個字元順序是從低位到高位,
這和我們平時書寫的順序是相反的,為了和書寫順序保持一致,在最後階段需要將字串首尾交換。
步驟1: 將緩衝區首地址p和head
步驟2:
c取n除以r的餘數,即c=n % r
將c存入p處
p前進一個位置, 即p=p+1;
n取n除以r的商, 即n=n/r;
步驟3: 如果n大於0,繼續重複執行步驟2
步驟4:
將字串倒置,即末字元和第1個字元交換,倒數第2個字元和第2個字元交換,依次類推。如"12345"經過倒置後變成"54321"
到此為止,以head開頭長度為p-head的字串即為n的R進位表示。
下面是該演算法的c語言代碼,以16進位為例
char hextab[]={ '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};// 將數n轉化為16進位字串,存入buffvoid toHex(int n,char buff[]){ char *p=buff; char *head; do { int c; c=n % 16; *p=hextab[c]; p++; n=n / 16; } while (n>0); *p=0; //c語言字串結束標誌 p--; //p指向最後一個字元 head=buff; while (head<p) { char tmp=*head; *head=*p; *p=tmp; head++; p--; }}
這裡再給出轉換為10進位字串的c語言代碼
// 將數n轉化為10進位字串,存入buffvoid toDec(int n,char buff[]){ char *p=buff; char *head; do { int c; c=n % 10; *p=c+'0'; p++; n=n / 10; } while (n>0); *p=0; //c語言字串結束標誌 p--; //p指向最後一個字元 head=buff; while (head<p) { char tmp=*head; *head=*p; *p=tmp; head++; p--; }}
下面是該演算法的16位組合語言代碼例子,以10進位為例
TITLE toDec .model smallstack segment db 100 dup(?)stack ends data segment n dw ? out_buff db 16 dup (?)data ends code segmentassume cs:code,ds:data,ss:stack start: mov ax,stack mov ss,ax mov ax,data mov ds,ax mov ax,9 ;here you can change 9 to any value mov word ptr n, ax mov di, offset out_buff call to_DecString mov dx, offset out_buff mov ah, 9h int 21h mov ax,4c00h ; terminate program int 21h ;conver a number result to output_buffer and fill 0x24 after number string;function:; conver 16 bit integer result to string;input parameter:; none, always convert varible n;output parameter; di: The address of output bufferto_DecString proc push di ;di is the address of out_buff mov bx,10 convert_loop: xor dx,dx ;0 -> dx mov ax,word ptr [n] div bx mov word ptr [n],ax add dl,'0' ;save (result % 10)+'0' to out_buffer mov byte ptr [di],dl inc di ;di point next position cmp_r: cmp word ptr [n],0 jnz convert_loopinv_string: ;swap string head and string tail dec di ;the di point to the last char mov byte ptr [di+1], 24h ; For dos ah=09, string output, the terminal char must be 0x24 pop si ;now si is the address of out_buff inv_loop: mov al,[si] ;swap char mov ah,[di] mov [di],al mov [si],ah inc si dec dicmp_head_tail: cmp si,di jb inv_loop retto_DecString endpcode ends end start
轉化R進位字串為1個數
1個R進位字串轉化為1個數,採用乘r加“字元”法,重複的計算n乘以R的積並加上當前字元,直到當前字元不是一個有效R進位字元。
步驟1: 將字串首地址送p, n=0
步驟2: 如果 p指向的字元不是R進位字元,轉步驟4
步驟3:
n和r相乘,結果仍然存入n
n和p指向的字元對應的數相加,結果仍然存入n
p前進一個位置, 即p=p+1;
轉步驟2
步驟4: 現在n已經是轉化好的數
這裡是10進位字串轉換整數的c語言代碼
//轉換decString到一個整數並返回int decString2Number(char *decString){ int n=0; char *p=decString; while ( *p>='0' && *p<='9') { n*=10; n += (*p-'0'); p++; } return n;}
下面給出一個16位dos彙編代碼,過程input_num從標準輸入裝置輸入一個字串,然後轉換這個字串到16bit整數,並儲存在ax寄存器
; 傳入參數: si, 輸入緩衝區地址, [si]儲存隨後緩衝區的大小; 傳出參數: ax, 轉化後的數input_num proc mov dx,si mov ah,0ah int 21h add si,2 ;skip the first 2 char and point to the first input char xor ax,ax ;ax return value xor cx,cx ;clean cx mov bx,10loop_read_char: mov cl,byte ptr [si] ; get a char from in_buff sub cl,'0' cmp cl,9 ja input_exit ;if the char <'0' or >'9', then jump out loop mul bx add ax,cx inc si jmp loop_read_charinput_exit: retinput_num endp
說明,上面的代碼稍稍使用了一些技巧,判斷字元是否在'0'到'9'之間只用了一條比較命令,下面解釋一下。
字元大於'9'則跳出迴圈,應該容易理解。但如果字元小於'0',是否也會跳出迴圈呢。答案是Yes. 如果字元cl小於'0',在執行完"sub cl,'0'"後,cl是一個負數,8bit的負數看作無符號數時,它一定比9大,所以同樣會跳出迴圈。
結束語,就到此為止吧。如果你想要一個包含輸入字串轉化為數,做某種計算,再轉化為字串輸出的16位組譯工具的例子,請在文章《我編的十進位乘法運算程式
可以4乘4 不知道哪有錯誤 請指教~~》查看完整的代碼.