這學(xué)期有了匯編課,但個(gè)人感覺這部分難度還是有一些的,所以寫了這篇簡(jiǎn)書當(dāng)做自己對(duì)于知識(shí)的復(fù)習(xí),同時(shí)也是希望能夠再次加深理解,雖然匯編語言在當(dāng)今用的比較少了,但是作為一名計(jì)算機(jī)專業(yè)的學(xué)生,我們還是應(yīng)該有所了解的。
我的這篇文章分為三個(gè)部分
第一部分:通過看MASM的默認(rèn)程序段,對(duì)一些基本概念略作了解
第二部分:通過解讀一道例題,了解簡(jiǎn)單的匯編指令(個(gè)人感覺跟高級(jí)語言還是有一些差異的)
第三部分:總結(jié)一下編寫匯編語言的大致思路
接下來進(jìn)入第一部分
正如高級(jí)語言有自己的編譯器一樣,匯編語言也有自己的一個(gè)編程環(huán)境,目測(cè)是有幾種不同的環(huán)境的。
我們學(xué)校課程選用的是MASM這個(gè)環(huán)境,我個(gè)人感覺還是不錯(cuò)的

首先打開編譯器是這個(gè)樣子的,這些都是默認(rèn)的

大致可以分成三個(gè)部分,data segment(數(shù)據(jù)段)、stacks segment(堆棧段)、codes segment(代碼段)
一般如果我們的程序里需要有pop和push這類的指令,那么我們要先寫堆棧段這部分(作為初學(xué)者就不用考慮這些了,先寫上,反正也不會(huì)有什么錯(cuò)。。。)

這里面 db 128? dup(0)表示的意思是開辟128個(gè)字節(jié)大?。ㄓ胐b表示)的空間,每一個(gè)空間的初始為0(有一點(diǎn)C語言數(shù)組的味道。。。)
基本上每一個(gè)匯編程序一開始都是寫這個(gè),但是為什么要這么寫,我個(gè)人認(rèn)為作為初學(xué)者暫且可以不去探究,只需要記住每次寫匯編之前,先寫上這么一段,以及知道這一段是在開辟一段空間,這段空間是作為堆棧使用的就可以
stack segment stack
db 128 dup(0)
stack segment
堆棧段就到此為止了,接下來我們進(jìn)入數(shù)據(jù)段的研究
數(shù)據(jù)段的初始是這個(gè)樣子的,我們要在其中定義我們需要用的數(shù)據(jù)
DATAS SEGMENT
?;此處輸入數(shù)據(jù)段代碼?
DATAS ENDS
舉個(gè)例子

比如下面這樣

目前來說我作為一個(gè)沒編過幾個(gè)程序的新手,我見到的大部分?jǐn)?shù)據(jù)定義格式都是這個(gè)樣子的
變量名? ? ? ? ?變量類型? ? ? ? ?初始值
num1? ? ? ? ? ? ?db? ? ? ? ? ? ? ? ? ?0
這一句就表示定義一個(gè)叫num1的變量,這個(gè)變量是字節(jié)形的,也就是說在內(nèi)存中為這個(gè)變量開辟一個(gè)8位的字節(jié)空間來保存這個(gè)變量的值
個(gè)人認(rèn)為需要注意這幾點(diǎn)
(a)變量名的定義不要和關(guān)鍵字沖突(比較好的方式就是每個(gè)變量都包含一個(gè)下劃線,這樣比較省事)
(b)變量類型不止db一種,還有有很多其他的,放一張網(wǎng)上的圖

除了以上這種變量的定義方法,字符串變量的定義也是很常見的,格式如下
變量名? ? ? ?db(字符串只能用db)? ? ? ?字符串

我覺得對(duì)于初學(xué)者,有必要解釋一下,這個(gè)字符串的定義在內(nèi)存中是如何對(duì)應(yīng)的
首先明確字符串由字符組成,字符包括字母、數(shù)字、一些特殊符號(hào),還有空格,回車,換行等等
每一個(gè)字符都有一個(gè)0-63之間的數(shù)字與之對(duì)應(yīng),這個(gè)對(duì)應(yīng)關(guān)系就是美國信息交換標(biāo)準(zhǔn)碼(ASCII碼)
接下來我們來梳理內(nèi)存的對(duì)應(yīng)關(guān)系,以上面這個(gè)定義為例
我個(gè)人目前的理解是,prompt1相當(dāng)于是一個(gè)指針,這個(gè)指針指向的是這個(gè)字符串的第一個(gè)字符,在這里面就是‘p’,之后在內(nèi)存中從低地址向高地址,依次放入‘l’、‘e’、‘a(chǎn)’、‘s’、‘e’........‘:’、‘$’
雖然我們?cè)趯懙臅r(shí)候?qū)懙氖亲址?,但是存在?nèi)存中存的就是字符對(duì)應(yīng)的ASCII碼值
由于一個(gè)存儲(chǔ)單位是8位,所以存一個(gè)63以內(nèi)的數(shù)字是綽綽有余的
這也就是為什么在定義字符串的時(shí)候都是用db,而不用dw之類的存儲(chǔ)類型
我理解的就是為了盡可能的節(jié)約內(nèi)存資源,畢竟就算計(jì)算機(jī)技術(shù)發(fā)展到了今天,內(nèi)存資源也不是隨隨便便就可以浪費(fèi)的。
還有一點(diǎn)需要關(guān)注的就是,整個(gè)字符串中的最后一個(gè)字符'$',這個(gè)是表示字符串的結(jié)尾,可以類比C語言字符串中的'/0'
上面我是用語言描述的方式表達(dá)了數(shù)據(jù)定義與內(nèi)存之間的關(guān)系
接下來再用圖來加深一下理解
首先是數(shù)據(jù)的
接下來是字符的

到此為止,已經(jīng)進(jìn)行了簡(jiǎn)單的數(shù)據(jù)段和堆棧段的定義方式,其中堆棧段的定義可以一開始就直接寫上,但是數(shù)據(jù)段的定義往往是在編程的過程中逐步完善的。
接下來,我們要學(xué)習(xí)一個(gè)詞MACRO
查查,有道詞典,就知道這是一個(gè)厲害的詞


“人如其名”,macro的功能也是強(qiáng)大的,接下來就學(xué)習(xí)一下匯編中的宏指令
先回答一個(gè)基本的問題
為什么要學(xué)習(xí)宏指令?
為了使程序更簡(jiǎn)潔呀?。。?!
為什么會(huì)使程序簡(jiǎn)潔呢??(我暫且留一個(gè)伏筆,稍后再說)
我們先以一個(gè)程序?yàn)槔?,介紹一下匯編中如何利用DOS功能調(diào)用來輸出一個(gè)字符串
比如說我們的目的是要輸出字符串到dos命令行窗口中(小黑框),我們要用匯編完成,我們會(huì)怎么做呢
我們會(huì)用DOS的功能調(diào)用,因?yàn)镈OS的功能表里有字符串輸出功能

這個(gè)DOS功能的使用要求我們要把數(shù)值9放到ah寄存器(ax寄存器的高八位)中,補(bǔ)一句,在調(diào)用DOS功能時(shí),AH寄存器中的值是用來區(qū)分調(diào)用哪一個(gè)DOS功能,9號(hào)就是字符串輸出功能。
接下來呢,我們會(huì)這么寫
lea dx, string(這個(gè)語句叫有效地址傳送指令,在這句話里,它將string這個(gè)字符串的首地址,放到了dx這個(gè)16位的寄存器中)
這里注意了?。。。?/p>

lea這個(gè)指令的介紹如下

這個(gè)指令是很友愛的,因?yàn)橹噶顚?duì)于oprd1和oprd2是沒有特殊要求的,這里所說的特殊要求是對(duì)于oprd并沒有指定只能使用哪一個(gè)寄存器,后面我們會(huì)看到有些指令對(duì)于操作數(shù)是有特殊要求的
但是?。。?!
我們這一條指令是必須要把oprd1寫成dx寄存器的
因?yàn)榍懊娴腄OS功能表告訴我們,9號(hào)功能會(huì)自動(dòng)根據(jù)DS:DX找到字符串并輸出
int 21h(這個(gè)語句先暫且理解為DOS的功能執(zhí)行,更深入的理解個(gè)人認(rèn)為暫且不需要,因?yàn)槲覀兠看味际菍戇@個(gè)。。。。。)
前面我們說了正常情況下輸出一個(gè)字符串我們?cè)撛趺醋觯ㄖ虚g我們插入了lea指令的介紹),但是有些語句我們是要在一個(gè)程序里反反復(fù)復(fù)用到的,比如字符輸入、字符輸出、或者是字符串輸入輸出這些
為了避免麻煩,我們希望能夠把這些功能打個(gè)包,想用的時(shí)候隨時(shí)用,并且不用總是寫那些重復(fù)的語句
這個(gè)包就是“宏(macro)”?。。。?!
這個(gè)包就是“宏(macro)”!?。。?!
這個(gè)包就是“宏(macro)”!?。。?!
好,接下來我們就看一下,如何進(jìn)行打包工作
打包大致就是定義一個(gè)宏的意思
定義宏的格式是

舉個(gè)例子——輸出字符串的“宏”
再舉個(gè)栗子

輸入字符的宏
原本是這個(gè)樣子的
mov ah 1
int 21h
寫成“宏”就是這個(gè)樣子的

一般我們會(huì)把宏定義寫在代碼段之前,數(shù)據(jù)段之后,寫在數(shù)據(jù)段之后是因?yàn)楹甓x中會(huì)用到數(shù)據(jù)段定義的變量,比如string,寫在代碼段之前是因?yàn)榇a段里會(huì)用到宏,所以需要事先定義
最后要說一下宏的缺點(diǎn)!?。。?!
宏的缺點(diǎn)就是形參只能是字符串或者是字符,如果是其他類型的變量的話就要寫成一個(gè)函數(shù)啦?。。。?/b>
接下來我們就要進(jìn)入最為驚心動(dòng)魄的code segment了!?。?/h1>

在MASM匯編環(huán)境下,新建一個(gè)文件,其中code segemnt的部分是這樣的

這里面有一些東西是寫好的,我們不需要自己動(dòng)手,但是還是要知道是什么意思,我個(gè)人認(rèn)為這也對(duì)我們后續(xù)的編程是有幫助的。
比如assume CS:CODES,DS:DATAS,SS:STACKS
其中CS、DS、SS是段寄存器(其實(shí)還有一個(gè)ES段寄存器,這里用不上)
ASSUME CS:CODES,DS:DATAS,SS:STACKS
這句話表明CODES這個(gè)段從現(xiàn)在開始就被程序認(rèn)為是段寄存器了,DATAS就被認(rèn)為是數(shù)據(jù)段寄存器了、而STACKS就被認(rèn)為是堆棧段寄存器了
或許我上面說的這些話有點(diǎn)兒像廢話(其實(shí)我也覺得有點(diǎn)兒)。。。。。。。

但還是有一些含義的,以下是我個(gè)人的理解,僅供參考
上面這些代碼表明:內(nèi)存是客觀的,但是對(duì)內(nèi)存的使用是人為的
也就是說我可以定義一個(gè)段CODES給CS作為代碼段
但是我如果高興的話,我也可以定義一個(gè)HAPPY段,然后寫CS:HAPPY(這是我個(gè)人理解,還沒有試驗(yàn)過。。。。),但是無論是HAPPY還是CODES他們發(fā)揮的功能都是存儲(chǔ)指令
接下來要解釋一下那個(gè)start是什么意思,要想理解為什么我們每一次寫代碼段的時(shí)候都要寫個(gè)start就要先理解,整個(gè)程序的最后那個(gè)END的含義。
END的含義表示程序的結(jié)束
格式就是 END 表達(dá)式(這里的表達(dá)式必須是程序段第一個(gè)可執(zhí)行指令的地址。這個(gè)很重要)
為了能夠得到這個(gè)地址,我們就要給程序第一個(gè)指令一個(gè)標(biāo)號(hào),這個(gè)標(biāo)號(hào)的含義就是第一個(gè)指令的位置
接下來進(jìn)入第二部分:解讀一道例題,了解幾個(gè)最基本的匯編指令
我以我的課內(nèi)作業(yè)為例
題目如下:
求兩個(gè)數(shù)的平方差,若是負(fù)數(shù),要輸出負(fù)號(hào)。要求:由鍵盤輸入兩整數(shù)a 、b,中間空格隔開;Enter 鍵結(jié)束輸入,并換行顯示結(jié)果。
按照我們前面說的,首先是一些無腦操作(在理解之后就可以無腦了)

第一步:堆棧段開辟,并賦初始值0
stack segment stack
db 128 dup(0)
stack ends
數(shù)據(jù)段先空著,宏定義這部分也先空著,后面根據(jù)需要再補(bǔ)(有需求,才有編程。。。)
我們先開始寫代碼段,先寫上加粗的兩句,這兩句基本上也屬于常規(guī)操作,基本所有程序的代碼段上來都是這個(gè)(只是個(gè)人經(jīng)驗(yàn),其實(shí)總共也沒編幾道題。。。。)
CODES SEGMENT
ASSUME CS:CODES,DS:DATA,SS:STACKS
START:
MOV SS STACKS
MOV DS,DATAS
?MOV AH,4CH//DOS功能,表示代碼結(jié)束
?INT 21H
CODES ENDS
END START
原因就是匯編是對(duì)寄存器的操作,寄存器一共有16個(gè),而寄存器的種類是有劃分的,其中段寄存器是用來存放段基址的(為什么會(huì)有這個(gè)概念是有歷史原因的,個(gè)人認(rèn)為可以暫且忽略,只需要知道段寄存器中的段基址是需要一開始就寫好的)
通過看這張圖,我們雖然不能掌握每一個(gè)寄存器的應(yīng)用,但是我們可以知道CS和SS兩個(gè)寄存器是很特殊的,他們叫段寄存器,是用來存放段基址的
但是到這里大體上的意思是對(duì)了,但是還有一點(diǎn)小問題
那就是MOV指令是不能把段名傳給段寄存器的,所以需要改一下,先把段名(段基址)傳給一個(gè)通用寄存器,再傳給段寄存器
MOV AX STACKS
MOV SS AX
MOV AX DATAS
MOV DS,AX
接下來為了簡(jiǎn)潔美觀,我們需要輸出提示語,為了鍛煉我們用宏的能力,我們就用宏來寫輸出提示語這一部分
首先是定義宏,宏的格式如下:
putstr(宏名) macro(宏關(guān)鍵詞) string(傳入的參數(shù))
lea dx string//把字符串的首地址傳入9號(hào)功能指定的寄存器 ,這里的string是形式參數(shù)(這個(gè)lea指令前面說過,英文源于LOAD EFFECT ADDRESS)
mov ah 09h//這里要調(diào)用dos的輸出字符串功能,是9號(hào)功能
int 21h
接下里就是在代碼段里調(diào)用宏
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX STACKS
MOV SS AX
MOV AX DATAS
MOV DS,AX
putstr prompt1//注意這里的prompt1是實(shí)際參數(shù),而這個(gè)實(shí)際參數(shù)是需要用前面介紹的數(shù)據(jù)段定義字符串的方式來定義的
MOV AH,4CH//DOS功能,表示代碼結(jié)束
INT 21H
CODES ENDS
END START
接下來按照程序的需求,該從控制臺(tái)讀入數(shù)字了
雖然我們說是從控制臺(tái)讀數(shù)字,但實(shí)際上讀的是字符串,而字符串是按照ASCII碼存的,也就是說內(nèi)存中實(shí)際上存的是ASCII碼,所以我們需要把讀入的字符ASCII變?yōu)閿?shù)值(這點(diǎn)看看ASCII表就知道是要把數(shù)字字符對(duì)應(yīng)的ASCII減去48就可以了,但是匯編中記得用16進(jìn)制喲?。∷允?0h)
這是一個(gè)經(jīng)常要做的事,所以寫成一個(gè)函數(shù)(為什么不寫成一個(gè)宏?答:這是由前面說過的宏只能傳入字符串的局限所導(dǎo)致的),函數(shù)的功能就是傳入字符,可以得到相應(yīng)的數(shù)值,以后直接調(diào)用即可
函數(shù)(匯編叫過程,從關(guān)鍵字proc(procedure就可以看出來))的標(biāo)準(zhǔn)格式是

接下來是具體的代碼,在這一部分只解釋代碼含義,不探討編程思維(也就是為什么這么寫的問題)
transform1(過程名) proc(這是關(guān)鍵字)
mov neg_flag,0//neg_flag是符號(hào)標(biāo)記(數(shù)據(jù)段要定義),規(guī)定0為正1為負(fù),初始neg_flag為0,相當(dāng)于是先默認(rèn)為正數(shù)
?可以跳過多余的空格,具體的操作就是利用循環(huán),跳過空格
skip_space://這是程序段的標(biāo)號(hào)(標(biāo)號(hào)我理解的就是一個(gè)類似于索引的東西,匯編可以很神奇地通過程序中的標(biāo)號(hào)找到標(biāo)號(hào)對(duì)應(yīng)程序的位置),用于跳轉(zhuǎn),可以跳過多余的空格,具體的操作就是利用循環(huán),跳過空格
getchar//由于經(jīng)常使用,所以定義一個(gè)宏,所以要定義一個(gè)宏,作用就是利用DOS功能01把輸入的字符對(duì)應(yīng)的ASCII碼值放到AL寄存器中
cmp al, ' ' ;//又是一個(gè)新指令,指令的作用就是通過前一個(gè)操作數(shù)減去后一個(gè)操作數(shù),之后根據(jù)運(yùn)算結(jié)果改變標(biāo)志位
je skip_space ;//又是一個(gè)新指令,這個(gè)指令英文:judge equal,原理就是看上一條指令運(yùn)算結(jié)果對(duì)于標(biāo)志位的改變,借此來判斷al是不是空格

這張圖沒有任何含義,我只是有點(diǎn)累了。。。。不想寫了。。。。。
在進(jìn)入下面這些語句的時(shí)候,AL中已經(jīng)有了值了,要么是數(shù)據(jù),要么是負(fù)號(hào),反正不是空格。。。
cmp al, '-';判斷有沒有負(fù)號(hào),跟空格的判斷一樣
jne next1;新指令(其實(shí)也不新,就是judge unequal,類比judge equal)若不相等,那么就是正數(shù),那么標(biāo)志位ZF=0,跳轉(zhuǎn)到next1標(biāo)號(hào)位置
mov neg_flag,1 ;這里是負(fù)數(shù)的標(biāo)記,如果上一條指令成立,那么不會(huì)走到這條指令,如果走到這里了,那就說明是負(fù)數(shù),于是就是neg_flag=1
getchar;獲取字符,此時(shí)應(yīng)該就是負(fù)數(shù)的數(shù)字了
next1:
mov di, 0 ;di來保存換成的數(shù)值,di本身是目的地址寄存器,但由于屬于通用寄存器,所以也可以用來存數(shù)據(jù),注意此時(shí)的di是一個(gè)16位的寄存器,這一點(diǎn)對(duì)于后面的程序是有影響的
input_loop:看這個(gè)樣子就知道是一個(gè)用于循環(huán)的程序標(biāo)號(hào)
sub al, 30h; (0-9)asc碼轉(zhuǎn) 數(shù)值,我們以3為例
mov cl, al ; 轉(zhuǎn)出的數(shù)值保存到cl(8位寄存器到8位寄存器),此時(shí)cl=3,al=3
mov ax, di; 此時(shí)ax=0,注意上一行al=3,但此時(shí)ax=0使得al也等于0了,因?yàn)閍x中包含al
mov bx, 10;bx=10
mul bx? ? ;又是一條新指令,mul做的是無符號(hào)的乘法,具體操作要看操作數(shù)是字還是字節(jié),如果操作數(shù)是字節(jié)操作數(shù),則把AL中的無符號(hào)數(shù)與SRC相乘得到16位結(jié)果送AX中,即:AX←(AL)*(SRC)。如果SRC是字操作數(shù),則把AX中的無符號(hào)數(shù)與SRC相乘得到32位結(jié)果送DX和AX中,DX存高16位,AX存低16位,我們這里是字操作數(shù)所以結(jié)果存到了DX:AX兩個(gè)寄存器中,此時(shí)DX、AX都是零
mov ch,0;讓ch(8位寄存器為)為0
add ax, cx ;之前的di的數(shù)值+ 轉(zhuǎn)后的數(shù)值,此時(shí)ax結(jié)果為3
mov di, ax ;保存結(jié)果到di,此時(shí)di=3,ax=3
getchar;接著獲取字符,字符的ASCII放到AL中
cmp al,0dh ;遇到回車,認(rèn)為輸入完一個(gè)整數(shù),在這部分由于DOS功能默認(rèn)選擇AL寄存器為輸入字符的存儲(chǔ)位置,因此破壞了之前AX=3
je int_quit
cmp al,' ' ;遇到空格
je int_quit? ?
jmp input_loop;相當(dāng)于是無條件的跳轉(zhuǎn)
int_quit:
mov ax,di ;save di to ax,這里恢復(fù)之前由DOS功能導(dǎo)致的AX值的改變,讓AX重新為結(jié)果3(這就告訴我們?nèi)绻覀兿胍袮X作為結(jié)果,那么中間一旦使用DOS的某些功能就一定要把AX中的值先進(jìn)行保護(hù),這里我們用的是di進(jìn)行了保護(hù))
mov bx, neg_flag;把符號(hào)標(biāo)記給bx
cmp bx, 1;看bx是否為正數(shù)
jne LN;若不是正數(shù),跳到LN
neg ax ;又是新指令,但并不復(fù)雜,就是取反而已,放在負(fù)數(shù)的判斷之后
LN:
ret
transform2
?endp
注意
(1)在這個(gè)過程中要注意對(duì)于寄存器高低位的使用,Ax=AH與AL拼接,所以即便AL之前有值,如果之后對(duì)AX賦值0,那么AL也將是0
(2)MUL這個(gè)指令做的是無符號(hào)的乘法,所以是絕對(duì)值相乘,結(jié)果都是正數(shù),如果是負(fù)數(shù)的話,之后要再用neg取反
現(xiàn)在通過這個(gè)函數(shù),數(shù)值3已經(jīng)放到了AX里了,同理,再調(diào)用一次另外一個(gè)數(shù)值,暫且定為4,也可以得到,由于兩次都是放在AX中,所以一定要定義其他的變量,以便保存數(shù)據(jù)
call transform1
mov num1, ax第一個(gè)數(shù)
call transform1
mov num2, ax第二個(gè)數(shù)
mov ax, num1由于后面要做平方,所以有一個(gè)數(shù)必須放到AX里,另外一個(gè)數(shù)可以隨便放
mov bx, num1此時(shí)放到了BX里
mul bx無符號(hào)乘法,字操作數(shù),所以結(jié)果放到DX:AX中
mov square_num1, ax但是在這里我們只取AX中的數(shù)據(jù),這就造成了超出一定的范圍之后,結(jié)果會(huì)錯(cuò)誤,不過100以內(nèi)的演示是沒有問題的
mov ax, num2
mov bx, num2
mul bx
mov square_num2, ax
mov ax, square_num1
sub ax, square_num2
現(xiàn)在我們已經(jīng)得到了結(jié)果了,接下來是把這個(gè)結(jié)果顯示到屏幕上,顯示的是字符,所以我們要把結(jié)果的數(shù)值變?yōu)閷?duì)應(yīng)的ASCII值
為了讓主程序簡(jiǎn)潔,所以我們就再寫一個(gè)函數(shù)
;整數(shù)值轉(zhuǎn)asc碼
transform2 proc
cmp ax, 0
jge LW;新的一條指令,當(dāng)大于等于時(shí)轉(zhuǎn)移
neg ax? ? ? ;如果是負(fù)數(shù),轉(zhuǎn)成正數(shù)
putchar '-' ;調(diào)用宏輸出負(fù)號(hào)標(biāo)記
LW:
mov cx, 0; cx清零待用,功能是看數(shù)直結(jié)果需要幾個(gè)字符來表示
?cal_loop:一看就是循環(huán)用的標(biāo)號(hào)
mov dx,0? ;dx清0,準(zhǔn)備除法
mov bx,10
div bx? ? ;整數(shù)除以10,div指令是無符號(hào)除法,分為字節(jié)型與字型兩種,具體用哪一種取決于div reg中reg這個(gè)寄存器的位數(shù),其中字節(jié)除法的結(jié)果商在al中,余數(shù)在ah中,字除法商在ax中,余數(shù)在dx中,所以當(dāng)前這句話的余數(shù)在dx中
push dx? ? ;余數(shù)dx入棧
inc cx? ? ;新指令,就是相當(dāng)于i++,是一個(gè)自加的指令,指令每運(yùn)行一次cx加一計(jì),算需要幾位asc2碼 (前面清零是為了這里用)
cmp ax,0
jne cal_loop;只要ax不等于0,就一直取出余數(shù)
show_loop:
pop dx ;取出余數(shù)
add dl,30h; '0'把余數(shù)的數(shù)值對(duì)應(yīng)的ASCII碼算出來
putchar dl 調(diào)用宏打印字符
loop? show_loop;新指令,很重要,是循環(huán)語句的重要部分,之前我們的那種跳轉(zhuǎn)循環(huán)是利用自己的cmp指令來控制的(相當(dāng)于while),而現(xiàn)在可以用cx的計(jì)數(shù)來控制(相當(dāng)于for循環(huán)),loop每循環(huán)一次cx就會(huì)減一,當(dāng)cx=0時(shí),循環(huán)結(jié)束跳出
ret
WriteInt?
endp
最后是主函數(shù)
mov ax, stack
mov ss, ax
mov ax, data
mov ds, ax?
putstr prompt1
?;輸入第一個(gè)數(shù)?
?call transform1
mov num1, ax
;輸入第2個(gè)數(shù)?
call transform1
mov num2, ax
mov ax, num1
mov bx, num1
mul bx
mov square_num1, ax
mov ax, num2
mov bx, num2
mul bx
mov square_num2, ax
mov ax, square_num1
sub ax, square_num2
call transform2
?mov ah, 4ch ;返回dos
int 21h
到此我們把所有重要的部分都過了一遍了,代碼我基本也把注釋寫清楚了,只要自己找兩個(gè)數(shù)據(jù)自己人肉編譯一下,基本就理解了。
接下來是最后一部分,大致理解一下匯編思路
一般來說一個(gè)程序需求我們都是可以理解的,我感覺難點(diǎn)在于如何用匯編語言描述,這次這個(gè)例題中如何將一個(gè)字符,轉(zhuǎn)為對(duì)應(yīng)的數(shù)值比較難的,看似一個(gè)減48的操作并不難,但是由于要處理正負(fù)號(hào)、回車、空格的問題導(dǎo)致有些寄存器中的數(shù)據(jù)需要被保護(hù),進(jìn)而增加了程序的復(fù)雜性,所以在匯編中要時(shí)刻保持對(duì)于寄存器的敏銳,時(shí)刻關(guān)注指令對(duì)于寄存器的影響(尤其是那些有默認(rèn)寄存器的指令比如mul,甚至是DOS功能下的默認(rèn)寄存器使用都是需要注意的)
2018.5.8補(bǔ)
前面所寫的這些在正確性上或許會(huì)有不嚴(yán)謹(jǐn)甚至是有疏漏的地方,后續(xù)我還會(huì)再查一遍
今天想說的是,之前有幾點(diǎn)我沒有注意到,今天在我匯編老師的指導(dǎo)下才注意到
(1)程序中缺少“邊界限制”,這個(gè)限制指的就是對(duì)于用戶不合法的輸出,程序應(yīng)該能夠處理
舉個(gè)例子:
比如明明應(yīng)該輸入0-9的字符,結(jié)果輸入了一個(gè)‘#’,這個(gè)時(shí)候程序應(yīng)該給出一定的反饋,比如
input error這種提示語,并且讓用戶重新輸入才行
(2)第二個(gè)就是要通過測(cè)試來發(fā)現(xiàn)自己程序的缺陷,并且最好改正過來
舉例
比如你的程序只能輸出單一字符,不能輸出12 345這種,那我最好就要考慮如何解決這個(gè)問題(千萬不要只是完成功能就完了,那樣不太好。。。)