彙編語言階段一總結

將數據代碼放入不同的段

彙編語言程序可以將數據,棧和代碼都放到一個段裏面,但是也可以將程序,棧和代碼分別放到不同的段裏,下圖就是定義多個段的程序

assume cs:codesg,ds:data,ss:stack

data segment
	dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends

stack segment
	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

codesg segment
start:mov ax,stack
	  mov ss,ax
	  mov sp,20h   ;設置棧頂指向stack:20
	  
	  mov ax,data
	  mov ds,ax    ;設置ds指向data段
	  
	  mov bx,0
	  mov cx,8
	s:push [bx]
	  add bx,2
	  loop s
	  
	  mov bx,0
	  mov cx,8
   s0:pop [bx]
      add bx,2
	  loop s0

	  mov ax,4c00h
      int 21h	  
codesg ends

end start

上述程序定義了三個段,分別是:

  • 存放數據的數據段
  • 存放堆棧數據的堆棧段
  • 存放程序的代碼段

上圖所示的代碼中有一個需要注意的地方是堆棧指針的取值,也就是

mov sp,20h

要將明白sp的取值,就不得不明白數據在內存中的存儲形式,下圖示字在內存中的存儲,內存中存儲的數據分別是:4E20H和0012H。這裏要明白字和字節的換算關係:

1字節 = 8位 (1byte = 8bite)
1字 = 2字節 = 16位 (1word = 2byte = 16bite)

在這裏插入圖片描述
由上圖可以知道,內存中存儲4E20H這個數據時,是把4E20這個數據拆分成了兩個數據進行分別存儲,4E和20,並將高位存儲在高地址中,把低位存儲在低地址中。
回過頭來,再來分析SP的值,我們可以知道在初始化時Stack中存放了如下所示的數據:

	dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

上述數據是字型的,也就是一個數據有2個字節,因此,棧中所有的數據所佔的存儲空間是16*2 = 32個字節,而32是10進制數,轉換爲16進制也就是20H,,因此堆棧指針的初始值應該設置爲20H。

and 和 or指令

爲了更好的解釋and和or指令,這裏通過大小寫字符轉換的問題爲例子來說明這兩個指令的作用。

大小寫字母的關係

要實現大小寫字符的轉換,那就需要知道大小寫字符之間的關係是什麼。我們分別列出大小寫字符的ASCII碼,如下表所示:

大寫 十六進制 二進制 小寫 十六進制 二進制
A 41 01000001 a 61 01100001
B 42 01000010 b 62 01100010
C 43 01000011 c 63 01100011

通過對比,我們可以發現。小寫字母的ASCII碼比都比大寫字母的ASCII碼要大20H,這反映在二進制上,也就是大寫字母的第六位總是0,小寫字母的第六位總是1,因此,要實現大小寫字母的轉換,只需要將小寫字母的第六位變爲0即可。
下面代碼關於and和or的例子:

assume cs:codesg,ds:datasg

datasg segment
	db 'BaSiC'        ;將這裏的大寫字母轉換爲小寫字母
	db 'iNfOrMaTiOn'  ;將這裏的小寫字母轉換爲大寫字母
datasg ends

codesg segment
start:mov ax,datasg
	  mov ds,ax
	  
	  mov bx,0
	  mov cx,5
	s:mov al,[bx]
	  or al,00100000b
	  mov [bx],al
	  inc bx
	  loop s
	  
	   mov bx,5
	   mov cx,11
	s1:mov al,[bx]
	   and al,11011111b
	   mov [bx],al
	   inc bx
	   loop s1
	   
	   mov ax,4c00h
	   int 21h
codesg ends

end start

用[bx+idata]的方式進行數組的處理

上述程序我們用[bx]的方式來指明一個內存單元,還可以用一種更加靈活的方式來指明內存單元:[bx+idata]表示一個內存單元,它的偏移地址爲(bx)+idata。下面舉一個例子說明這個語句的用法:

mov ax,[bx+200]

上述語句的含義是:將一個內存單元的內容送入ax,這個內存單元的長度爲2個字節(字單元),存放一個字。偏移地址爲bx中的數值加上200,段地址在ds中。
數學描述如下:

(ax) = ((ds)*16+(bx)+200)

下圖所示代碼是用[bx+idata]的方式進行數組的處理

assume cs:codeseg,ds:dataseg

dataseg segment
	db 'BaSiC'  ;大寫變小寫 0->1
	db 'MinIX'  ;小寫變大寫 1->0
dataseg ends

codeseg segment
start:mov ax,dataseg
      mov ds,ax
	  
	  mov bx,0
	  mov cx,5
	s:mov al,[bx]
	 ;mov al,0[bx]
	  or al,00100000b
	  mov [bx],al
	 ;mov 0[bx],al
	  mov al,[bx+5]
	 ;mov al,5[bx]
	  and al,11011111b
	  mov [bx+5],al
	 ;mov 5[bx],al
	  inc bx
	  loop s
	  
	  mov ax,4c00h
	  int 21h
codeseg ends

end start

添加了[bx+idata]的方式,實現了類似於C原因數組的效果,其中註釋掉的代碼是作爲[bx+idata]的另一種表達形式。也是可以進行運行的。

SI 和 DI

si和di是8086CPU中和bx功能相近的寄存器,需要注意的是si和di不能夠分成兩個8位寄存器來使用。下面的三組指令實現的同樣的功能

(1)mov bx,0
   mov ax,[bx]
(2)mov si,0
   mov ax,[si]
(3)mov di,0
   mov ax,[di]

下面的代碼利用si和di將字符串複製到它後面的區域中去:

assume cs:codeseg,ds:datasg

datasg segment
	db 'welcome to masm!'
	db '................'
datasg ends

codeseg segment
start:mov ax,datasg
      mov ds,ax
	  
	  mov si,0
	  mov cx,8
	s:mov ax,[si]
	 ;mov ax,0[si]
	  mov [si+16],ax
	 ;mov 16[si],ax
	  add si,2
	  loop s
	  
	  mov ax,4c00h
	  int 21h
codeseg ends

end start

上述代碼中運用來了si實現了同bx一樣的功能,但是與bx不同的一點是,si只實現以字爲單位的運算,因此,雖要複製得字符串有16個字節,但是loop循環的次數卻只有8次。

[bx+si]和[bx+di]

在上述的代碼中,我們使用了形如[bx(si或di)]和[bx(si或di)+idata]的方式來進行尋址,這裏介紹一種更爲靈活的尋址方式:[bx+si]、[bx+di]
現有如下指令:

mov ax,[bx+si]

上述指令的含義是:將一個內存單元送入ax,這個內存單元的長度是2字節(字單元),存放一個字,偏移地址爲bx中數值加上si中的數值,段地址在ds中。數學化表示爲:

(ax) = ((ds)*16+(bx)+(si))

該指令也可以寫成如下常用的形式:

mov ax,[bx][si]

下面爲[bx+si指令的簡單運用:

mov ax,2000h
mov ds,ax
mov bx,1000h
mov si,0
mov ax,[bx][si]
inc si
mov cx,[bx][si]
inc si
mov di,si
add cx,[bx][di]

上述代碼的主要作用就是分別訪問了2000:1000、2000:1001、2000:1002的地址的數據,分別將其賦值給ax,cx。

[bx+si+idata]和[bx+di+idata]

[bx+si+idata]表示一個內存單元,它的偏移地址爲(bx)+si+idata。
現有人如下指令

mov ax,[bx+si+idata]

用數學表達式描述爲:

(ax)=((ds)*16+(bx)+(si)+idata)

該指令通常也可以寫成如下的形式:

mov ax,[bx][si].200

下面試運用[bx+si+idata]處理問題的相關程序

assume cs:codesg,ss:stacksg,ds:datasg

stacksg segment
  dw 0,0,0,0,0,0,0,0
stacksg ends

datasg segment
  db '1. display      '
  db '2. brows        '
  db '3. replace      '
  db '4. modify       '
datasg ends

codesg segment

	start:mov ax,stacksg
	      mov ss,ax
		  mov sp,16
		  mov ax,datasg
		  mov ds,ax
		  mov bx,0
		  
		  mov cx,4
		  
	   s0:push cx
	      mov si,0
		  mov cx,4
		  
		s:mov ax,[bx][si].3
		 ;mov ax,[bx+3+si]
		  and al,11011111b
		 ;mov [bx+3+si],ax
		  mov [bx][si].3,ax
		  inc si
		  loop s
		  
		  add bx,16
		  pop cx
		  loop s0
		  
		  mov ax,4c00h
		  int 21h
		  
codesg ends

end start

上述代碼的作用是將dataseg段的每個單詞前的四個字母改爲大寫字母,程序的實現思路是採用瞭如下的幾種方法

  • 循環嵌套的方法。
    • 第一層循環用於控制對哪一個單詞進行尋址,用於每一個單詞的字節大小均爲16,因此在操作完第一個單詞後,只需要將bx加16就可定位到下一個單詞。
    • 第二層循環用於控制對單詞的各個字母進行尋址,從而能夠改變到每一個單詞的各個字母。
  • 堆棧保存循環次數
    • cx值壓棧。由於控制彙編語言進行循環次數的寄存器均採用的是cx寄存器,因此,在第二層循環執行前必須將cx的值保存下來,這裏採用堆棧的機制保存cx的值,在第二層循環執行前將cx的值進行壓棧。
      • cx值出棧。 然後爲了能夠使第一層循環正常運行,在第二層循環執行完一次後,需要將堆棧裏的值彈出,賦值給cx,從而保證了第一層循環的正確運行。

指令要處理的數據長度

8086CPU中可以處理兩種長度的數據,因此,在機器指令中要指明處理的數據長度是多長的。在彙編語言中,主要存在如下幾種方法來指明數據的長度:

  • 通過寄存器名指明要處理的數據的尺寸
    • 字操作:mov ax,1 、mov ds,ax
    • 字節操作:mov al,1、mov al,bl
  • 在沒有寄存器名存在的情況下,用操作符X pyr指明內存單元的長度
    • 字操作:mov word ptr ds:[0],1、inc word ptr [bx]
    • 字節操作:mov byte ptrr ds:[0],1、inc byte ptr ds:[0]

div指令

div是除法指令,使用div的時候應該注意如下的問題:

  • 除數:有8位和16位兩種,在一個reg或內存單元中。
  • 被除數:默認放在AX或DX和AX中,如果除數爲8位,被除數則爲16位,默認在AX中存放;如果除數爲16位,被除數則爲32位,在DX和AX中存放,DX中存放高16位,AX存放低16位
  • 結果:如果除數爲8位,則AL存儲除法操作的商,AH存儲除法操作的餘數;如果除數爲16位,則AX存儲除法操作的商,DX存儲除法操作的餘數。

具體操作例子:

div byte ptr ds:[0]

含義:
(al) = (ax)/((ds)*16+0)的商
(ah)=(ax)/((ds)*16+0)的餘數

div word ptr es:[0]

含義:
(ax)= [(dx)*10000H+(ax)]/((es)*16+0)的商
(ax)= [(dx)*10000H+(ax)]/((es)*16+0)的餘數

僞指令dd,db,dw,dup

  • db:字節型數據
  • dw:字型數據
  • dd:定義雙字型數據

比如如下例子:

data segment
	dd 100001
	dw 100
	dw 0
data ends
  • dup:dup是一個操作符,在彙編語言中同db、dw、dd等一樣,也是由編譯器識別處理的符號。

比如如下例子:

db 3 dup (0)

相當於 db 0,0,0

db 3 dup (0,1,2)

相當於 db 0,1,2,0,1,2,0,1,2
使用dd能夠使得程序變得更加簡潔,簡短。

綜合例子

尋址方式在結構化數據訪問中的應用

詳細的程序代碼:

assume cs:codeseg,ds:dataseg,es:tableseg

dataseg segment
	db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
	db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
	db '1993','1994','1995'
	;以上是表示21年的21個字符
	
	dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
	dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
	;以上是表示21年公司總收入的21個dword型數據
	
	dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
	dw 11542,14430,15257,17800
	;以上表示21年公司僱員人數的21個word型數據
dataseg ends

tableseg segment
	db 21 dup ('year summ ne ?? ')
tableseg ends

codeseg segment
start:mov ax,dataseg
      mov ds,ax
	  
	  mov ax,tableseg
	  mov es,ax
	  
	  mov bx,0
	  mov si,0
	  mov di,0
	  
	  mov cx,21
	s:mov ax,[bx]
	  mov es:[si],ax   ;先將年份的低位存入
	  mov ax,[bx+2]
	  mov es:[si+2],ax ;後將年份的高位存入
	  
	  mov ax,[bx+84]
	  mov es:[si+5],ax ;先將總收入的低位存入
	  mov ax,[bx+84+2]
	  mov es:[si+7],ax ;後將總收入的高位存入
	  
	  mov ax,[di+168]
	  mov es:[si+10],ax ;將僱員人數的低位存入
	  
	  mov ax,[bx+84]    ;將總收入的低位存入ax
	  mov dx,[bx+84+2]  ;將總收入的高位存入dx
	  div word ptr ds:[di+168]
	  mov es:[si+13],ax
	  
	  add si,16
	  add bx,4
	  add di,2
	  
	  loop s
	  
	  mov ax,4c00h
	  int 21h
codeseg ends

end start

下圖是代碼運行結束後內存中各個數據的存儲位置

由圖中可以看出:

  • (1)號標號所框選的內容就是具體的年份存儲的形式,因爲年份在存儲的時候採用的是字符存儲的方式,每一個數字佔一個字節,一共佔了4個字節。另外,字符在計算機內存中存儲時採用的是以ASCII碼的形式進行存儲,圖中的每一個數字代碼一個字節。
  • (2)號標號中所框選的內容就是年份存儲年份後的一個空格,空格在內存中的存儲值是20H。因此,對於table列表中的其他空格存儲的值都是20H。圖中的第四列,第六列,第8列也都是存儲的空格。
  • (3)號標號中存儲的是總的收入,因爲總的收入需要佔用4個字節,因此,也就需要四列來存儲所有的收入指。其中,在進行讀取的時候,要遵循高位數據存放在高位內存中,低位數據存放在低位內存中。例如,在讀取最後一個數據時,正確的順序應該是00 5A 97 68
  • (5)號標號存儲的內容是公司的僱員數,僱員數佔2個字節,也就是圖中佔兩列
  • (7)號標號存儲的內容是公司員工的人均收入,佔兩個字節。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章