單片機彙編語言彙編錯誤原因分析
彙編語言的指令格式,目前有兩種不同的標準:Windows下的彙編語言基本上都遵循Intel風格的語法,比如:MASM、NASM;而Unix/Linux下的彙編語言基本上都遵循AT&T風格的語法;
一、彙編語言語句的通用格式
[名稱[:]] 指令碼 [第一操作數][,第二操作數] ;注釋
彙編語言的指令碼的操作數的個數可以是0、1、2個;當操作數的個數為2的時候,語句還有兩種不同的格式:
Windows下Intel風格的彙編語言語句格式為:
[名稱[:]] 指令碼 目的操作數DST,源操作數SRC ;注釋
Unix/Linux下AT&T風格的彙編語言語句格式為:
[名稱[:]] 指令碼 源操作數SRC,目的操作數DST ;注釋
例如: CYCLE: ADD AX,02H ;(AX)彙編語言語句格式中的"名稱"並不是所有語句都必需的,但是,如果語句中帶有"名稱",那麼,大多數情況下,"名稱"都表示的是內存中某一存儲單元的地址,也就是"名稱"後面各項在內存中存放的第一個存儲單元的地址(包括該"名稱"所在段的段地址和段內偏移地址);比如上面的指令中,CYCLE就是該語句的名稱,CYCLE表示的就是其後面的機器指令碼在內存中存放的第一個地址;"名稱"與指令碼之間的分隔符可以是冒號":",也可以是空格字符" ";當以冒號分割時,該名稱代表的是一個標號;當以空格分割時,該名稱代表的可能是標號,也可能是變量;當指令碼有多個操作數的時候,相鄰兩個操作數之間要用逗號","分割;指令碼與操作數之間必須以空格分割;彙編語言語句的注釋必須以分號";"開頭;
二、組成語句的元素
1、常數:
彙編語言中的常數有整數、字符串;二進位、八進位、十進位、十六進位;彙編語言採用不同的後綴區分:
B:二進位數; O:八進位數; D:十進位數; H:十六進位數;
當一個數值後面沒有後綴的時候,默認為十進位數;
字符串常數是用一對單引號('')括起來的一串字符;
2、表達式:
由操作數和操作符組成;
算數運算操作符: +、-、*、/、MOD,等;取模運算MOD是取兩數相除的餘數;
邏輯運算操作符: AND(邏輯與)、OR(邏輯或)、NOT(邏輯非)、XOR(邏輯異或);
注意:邏輯運算符同時又可以是邏輯運算指令的指令碼,只有當它們出現在指令的操作數部分時,才是操作符;例如:
ADD AL,0CH ADD 0FH ;第一個ADD是指令碼,第二個ADD是操作符;
關係運算操作符: EQ(相等)、NE(不等)、LT(小於)、GT(大於)、LE(小於等於)、GE(大於等於);
彙編語言中的表達式不能單獨構成語句,只能是語句的組成部分;
注意:語句中表達式的求值不是在語句執行時完成的,而是在對源程序進行彙編連結時完成的.所以,語句中各表達式的值必須在彙編或連結時就是確定的,也就是說,表達式中各標識符的值在彙編或連結時就應該是確定的;
3、標號:
標號是由標識符表示的指令的名稱,用於指示對應指令的位置(地址);
標號具有三個屬性:段地址、偏移地址和類型;
標號的段地址和偏移地址屬性是指該標號所對應的指令所在段的段地址和段內偏移地址;
標號的類型有兩種:NEAR和FAR;標號定義成NEAR類型,表示該標號在段內使用,而定義成FAR類型則表示該標號可以在段間使用;
標號的定義:在指令碼前面加上標識符和冒號":";
例如:START: PUSH DS
這條語句裡面,START就是我們定義的標號,它代表指令PUSH的地址,所以,標號可以作為程序轉移指令的操作數(即:要轉向的地址);標號還可以採用偽指令來定義;例如:用LABEL偽指令和過程定義偽指令來定義;
4、變量:
與高級語言一樣,並不是所有的操作數都是常數,彙編語言也有自己的變量,變量的值在程序運行期間是可以被改變的;
A.定義變量:彙編語言中,變量的定義是通過偽指令來完成的;定義變量的偽指令格式如下:
變量名 DB 表達式 ;定義字節變量,又稱單字節變量(1個連續字節),DB-->BYTE
變量名 DW 表達式 ;定義字變量,又稱雙字節變量(2個連續字節),DW-->WORD
變量名 DD 表達式 ;定義雙字變量,又稱四字節變量(4個連續字節),DD-->DWORD
變量名 DF 表達式 ;定義六字節變量,又稱六字節變量(6個連續字節),DF-->FWORD
變量名 DQ 表達式 ;定義長字變量,又稱八字節變量(8個連續字節),DQ-->QWORD
變量名 DT 表達式 ;定義十字節變量(10個連續字節),DT-->TBYTE;
其中,變量名是一個合法的標識符,變量名後面不能加冒號":",只能用空格;變量名不是必要的,可有可無;變量的類型由關鍵字DB、DW、DD、DQ、DT來定義;
變量定義語句中的"表達式"是用於對變量進行初始化的,可有一下幾種情況:
(1).一個或多個常數或表達式;當為多個常數或表達式時,期間要用逗號隔開;如DATA1--DATA4;
(2).帶單引號的字符串;
對於字節型(DB)變量,每個變量的大小為1個字節,每個變量的值不能超過1個字符,每個字節內存入一個字符的ASCII碼值,整個字符串可以在同一對單引號內給出,這相當於是定義了一個字符數組,如DATA5;
對於字類型(DW)變量,每個變量的大小為2個字節,每個變量的值不能超過2個字符,若為2個字符時,同樣遵循高位存入高字節,低位存入低字節的規則;若為1個字符,則該字符的ASCII碼值存入到低字節,高字節為00,如DATA6;
對於雙字類型(DD)變量,每個變量的大小為4個字節,每個變量的值不能超過2個字符,若為2個字符,同樣遵循高位存入高字節,低位存入低字節的規則;但是2個字符的值被存入到雙字變量的最低2個字節中,1個字符的值被存入到雙字變量的最低1個字節中;
對於長字類型(DQ)變量,每個變量的大小為8個字節,每個變量的值不能超過2個字符,若為2個字符,同樣遵循高位存入高字節,低位存入低字節的規則;但是2個字符的值被存入到長字變量的最低2個字節中,1個字符的值被存入到長字變量的最低1個字節中;
(3).一個問號"?",表示該變量的值不確定,即:該變量所表示的內存單元中的內容是不確定的,或者說是,當表達式為問號時,變量所對應的內存區中並沒有存入新的值,而只是預留出了相應的存儲空間;如DATA7、DATA8
(4).重複方式;此時的格式為: 重複次數 DUP(表達式);重複方式指出表達式的值可以重複地存儲到變量對應的內存區中,重複的次數由偽指令給出,相當於定義數組;如DATA9、DATA10
定義變量的例子:
DATA1 DB 20H ;1位元組變量
DATA2 DW 0204H,1000H ;2位元組變量
DATA3 DB (-1*3),(15/3) ;1位元組變量
DATA4 DD 123456H ;4位元組變量
DATA5 DB '0123' ;字符串變量,相當於一個字符數組
DATA6 DW 'AB','C','D' ;字符串變量,相當於一個字符串數組;
DATA7 DB ? ;1位元組變量,未初始化
DATA8 DD ? ;4位元組變量,未初始化
DATA9 DB 5 DUP(0) ;1位元組變量,用5個0初始化,相當於是一個具有5個DB型元素的數組
DATA10 DW 3 DUP(?) ;2位元組變量,未初始化,相當於是一個具有3個DW型元素的數組
變量定義語句中偽指令的功能是在變量名所對應的地址開始的內存區依次存入表達式中的各項值,表達式中的每項值所佔用內存字節數與變量的類型對應;
總結:一個變量的變量名實際上就代表了該變量所對應的內存區在內存段中的有效地址(偏移地址);高地址是指地址值相對較大,低地址是指地址值相對較小,高地址與低地址是相對而言的;
5、變量的屬性:
(1).屬性介紹
一個變量具有一下屬性:
A.段地址(SEG):變量所在段的段地址;
B.偏移地址(OFFSET):變量所在段內的偏移地址;
C.類型(TYPE):變量的類型定義了每個變量所佔用的內存字節數,對於DB、DW、DD、DQ、DT類型定義的變量所佔用的內存字節數分別是1、2、4、8、10;通常又將DB、DW、DD類型所定義的變量分別成為BYTE類型、WORD類型、DWORD類型變量;
常用標識符的類型值列表:
標識符種類 字節變量 字變量 雙字變量 近標號NEAR 遠標號FAR
TYPE的值 1 2 4 -1 -2
D.長度(LENGTH):變量定義時,一個變量名所定義的變量個數;在含有DUP操作符的變量定義中,變量名所定義的變量個數為定義格式中的重複次數;在其它各種變量定義中,每個變量名所定義的變量個數均為1個;
E.大小(SIZE):變量定義語句中,分配給同一個變量名的所有變量的總的字節數,其值為該變量的類型與長度的成績;
其中,段地址、偏移地址和類型屬性是變量的主屬性,而長度和大小屬性是變量的輔助屬性;
(2).屬性操作符:
操作符 表達式 含義
SEG SEG 變量名或標號 取出變量名或標號所在段的段地址
OFFSET OFFSET 變量名或標號 取出變量名或標號所在段內的偏移地址
TYPE TYPE 變量名或標號 取出變量名或標號的類型(變量所佔用的字節數)
LENGTH LENGTH 變量名 取出變量的長度
SIZE SIZE 變量名 取出變量的大小
這些操作符不能單獨構成語句,只能作為表達式的組成部分,並且表達式的求值也是在彙編過程中完成的;
6.強制類型轉換操作符PTR
格式:數據類型 PTR 地址表達式
格式中的"數據類型"可以是BYTE、WORD、DWORD、NEAR、FAR;前三種類型是變量的類型,後兩種類型是標號的類型;格式中的表達式可以是變量、標號、其它地址表達式;
PTR操作符的功能是用來重新定義已定義的變量或標號的類型,其作用域只在當前語句中; 例如:
DATA1 DW 02H
MOV BYTE PTR DATA1,AL
這條指令中,是把DATA1的類型轉換為BYTE類型,然後把AL中的內容存放到DATA1的最低一個字節中;作用域只在這條MOV語句中,過了這條語句,DATA1仍然是DW類型,即:DATA1原來的類型並沒有被修改;
7、複合數據類型:
符合數據類型,除了用DUP定義的重複數據類型之外,與C/C++語言一樣,彙編語言中也有結構體類型、聯合類型、記錄類型;
(1).結構體類型:
A.類型定義格式:
結構類型名 STRUC [對齊類型Alignment][,NONUNIQUE]
Field1 Type1 Exp1
Field2 Type2 Exp2
.
FieldN TypeN ExpN
結構類型名 ENDS
說明:結構體中的欄位名可有可無;若有欄位名,則欄位名必須唯一,每個欄位可獨立存取;若沒有欄位名,則通過偏移量來存取;
對齊方式Alignment:定義每個欄位的字節對齊邊界,對齊值有1、2、4、8、16位元組對齊,值必須是2的冪次方;對齊類似於C/C++中結構體欄位的對齊;
NONUNIQUE:要求結構體中的欄位必須用全名才能訪問;
結構體中的欄位可以有欄位名,也可以沒有欄位名;有欄位名的欄位可直接使用該欄位名來訪問,沒有欄位名的欄位可用使用該欄位在結構體中的偏移量來訪問;
例如:
PERSON STRUC
NO DD ? ;有名欄位,偏移量為0
NAME DB 10 DUP(?) ;有名欄位,偏移量為4
DB 1 ;無名欄位,偏移量為14
PERSON ENDS
B.結構類型變量的定義:
[變量名] 結構類型名
欄位值列表中的各個欄位之間用逗號","分割,欄位值的排列順序及類型應該與該結構定義時說明的各個欄位相一致;如果結構變量中某個欄位的值使用定義結構時說明的預設值,那麼可用逗號來表示;如果所有欄位都使用定義結構體時說明的各個欄位的預設值,則可省去欄位值列表,只需保留一對尖括號""即可;
例如:
Per1 PERSON ;所有欄位都是用默認值
Per2 PERSON ;所有欄位都重新初始化
Per3 PERSON ;第二個欄位使用默認值;
C.結構體類型欄位的引用:
格式: 結構變量名.欄位名
這種引用方式與高級語言中的引用方式完全一致;另外,還可以使用偏移量來訪問某個欄位;
方式1:使用欄位名直接引用
MOV AX,Per3.NAME
方式2:使用欄位的在結構體中的偏移量來引用
LEA SI,Per3 ;取變量Per3對應內存塊的有效地址
MOV AX,[SI+4] ;寄存器相對尋址,4是欄位NAME的偏移量
(2).聯合體類型:
A.類型定義格式:
[聯合體類型名] UNION [對齊方式Alignment][,NONUNIQUE]
Field1 Type1 Exp1
Field2 Type2 Exp2
.
FieldN TypeN ExpN
[聯合體類型名] ENDS
說明:聯合體類型中的各個欄位相互覆蓋,即:同樣的存儲單元被多個不同類型的欄位所對應,並且每個欄位在聯合體類型中的偏移量都是0;聯合體類型所佔用的字節數是其所有欄位所佔字節數的最大值,即:聯合體所佔用的字節數是這個聯合體的所有欄位中佔用字節數最多的那個欄位佔用的字節數;
對齊方式Alignment:可用1、2、4、8、16個字節來指定聯合體中各個欄位字節的對齊邊界,其預設的對齊邊界是1位元組;還可用使用偽指令ALIGN或EVEN來重新定界,也可使用命令行選項/Zp來定界;
NONUNIQUE:要求聯合體類型中的欄位必須使用全名才能訪問;
例如:
DATE UNION
YEAR DB 2010
MONTH DB 07
DAY DB 04
DATE ENDS
B.聯合體類型變量的定義:
聯合體類型的變量只能使用第一個欄位的數據類型來進行初始化;例如:
DATE1 DATE ;定義一個聯合體類型變量DATE1,並使用第一個欄位的數據類型進行初始化
DATE2 DATE ;初始化錯誤,只能使用第一個欄位的數據類型進行初始化;
C.聯合體類型欄位的引用:
格式: 聯合體類型變量名.欄位名
例如:
MOV DATE1.YEAR,2012 ;給聯合體類型變量欄位賦值
MOV AL,DATE1.MONTH ;AL=07
MOV BX,DATE1.YEAR ;BX=2012
MOV DATE1.MONTH,08 ;月份置為8月
(3).記錄類型:
A.類型定義格式:
彙編語言中的記錄類型與高級語言中的記錄類型不同,在彙編語言中,記錄類型是為按照二進位位存取數據提供方便的;記錄類型的說明要用到另一個關鍵字RECORD,格式如下:
記錄名 RECORD 欄位[,欄位,...]
其中,"欄位"代表: 欄位名:寬度[=初始值表達式]
說明:記錄名代表該記錄類型;記錄類型可以由多個欄位組成,相鄰兩個欄位之間用逗號隔開;記錄類型中欄位的屬性包括欄位名、寬度和初始值;記錄類型中,欄位的"寬度"屬性表示該欄位所佔用的二進位位數,它必須是一個常數,並且所有欄位的寬度之和不能大於16(即:有欄位的寬度之和大於8,則系統會自動為該記錄類型分配2位元組的空間,否則只分配1個字節的空間;記錄類型的最後一個欄位排在所分配空間的最低位,然後對記錄中的欄位依次"從右向左"分配二進位位,左邊沒有分完的二進位位自動補0;初值表達式給出的是該欄位的預設值,如果初值超過了該欄位所表示的範圍,那麼,在彙編時將產生錯誤提示信息,如果某欄位沒有初值表達式,則其初值為0;
例1:
COLOR RECORD BLINK:1,BACK:3=0,INTENSE:1=1,FORE:3
該COLOR類型的二進位位分布如下圖所示:
該類型的各個欄位寬度為:1、3、1、3,所以,該記錄佔用8個二進位位,系統為它分配1個字節;
例2:
FLOAT RECORD DSIGN:1,DATA:8,ESIGN:1,EXP:4
該FLOAT類型的二進位位分布如下圖所示:
該類型的總寬度是14個二進位位,所以,系統為它分配2個字節的空間;
B.記錄類型變量的定義:
[變量名] 記錄類型名
說明:變量名就是該記錄類型的變量名,它可預設,則不能使用符號名來訪問該內存單元;欄位值列表是用於給各個欄位賦初值,相鄰兩個欄位值之間用逗號","隔開,其欄位值的排列順序及大小應該按照記錄類型定義時說明的各個欄位的順序和大小來排列;如果記錄類型變量的某個欄位使用默認值,那麼,可用逗號來表示,如果所有欄位都是用默認值,則可省去欄位值列表,但必須保留一對尖括號"";
例如:
COLOR1 COLOR , ,
FLOAT1 FLOAT ,
C.記錄類型欄位的引用:
格式: 記錄類型變量名.欄位名
例如: MOV AL,COLOR1.FORE
D.記錄類型的專用操作符:
操作符WIDTH和MASK是專用於記錄類型的操作符,利用它們可用得到記錄類型的不同屬性;
WIDTH:用於返回記錄或其欄位的二進位位數,即:記錄類型或記錄類型欄位的寬度;書寫格式如下:
WIDTH 記錄名 或 WIDTH 記錄欄位名
例如:記錄類型COLOR,那麼,WIDTH COLOR的值為8,WIDTH BACK的值為3,WIDTH BLINK的值為1;
MASK:它返回一個8位或16位的二進位數,在該二進位數中,被指定記錄或欄位使用的對應位的值為1,否則,其值為0;書寫格式如下:
MASK 記錄名 或 記錄欄位名
例如:記錄類型FLOAT,那麼,MASK EXP的值為000FH,MASK DATA的值為1FE0H,MASK DSIGN的值為2000H;
記錄欄位:記錄欄位名是一個特殊的操作符,它本身也是一個操作數,其返回值是該欄位移到該欄位所在記錄的最低位所需要的位數,即:該欄位最低位在記錄中的位置;
例如:記錄類型FLOAT,則有:
MOV CL,EXP 相當於 MOV CL,0
MOV CL,DATA 相當於 MOV CL,5
(4).類型重定義:
已知某一數據類型,程式設計師可以定義這個數據類型的別名或指針類型.表達這種定義的偽指令是TYPEDEF,其定義形式如下:
新數據類型名 TYPEDEF [位距][PTR] 已知數據類型
其中,"位距"是NEAR、FAR、PROC等;
例如:
CHAR TYPEDEF BYTE ;給BYTE類型定義另外一個別名CHAR,C++中就是: typedef BYTE CHAR
PCHAR TYPEDEF PTR CHAR ;定義一個字符指針數據類型PCHAR,C++中就是:typedef PTR CHAR PCHAR,即:typedef char* PCHAR
那麼,下面的變量定義就是合法的了:
CH1 CHAR 'ABCDEF' ;定義一個字符串常量
PCH1 PCHAR CH1 ;定義一個指向字符串常量CH1的變量
這個功能類似於C++語言中的typedef語句;
8.表達式中的操作符:
HIGH(高8位)、LOW(低8位)
SEG(段地址)、OFFSET(偏移量)、TYPE(數據類型)、LENGTH(變量長度)、SIZE(變量容量)
WIDTH(記錄/記錄欄位的寬度)、MASK(記錄/記錄欄位的屏蔽位),等等;
其中,HIGH和LOW分別用於選取表達式計算結果的高8位和低8位,使用格式如下:
HIGH 表達式 LOW 表達式
9.運算符和操作符的優先級:
優先級: 高 LENGTH、SIZE、WIDTH、MASK、()、[]、.(用於結構欄位)、(用於記錄類型)
↓ PTR、SEG、OFFSET、TYPE、THIS、:(用於段超越前綴)
*、/、MOD、SHL、SHR
↓ HIGH、LOW
+、-
↓ EQ、NE、LT、LE、GT、GE
NOT
↓ AND
OR、XOR
優先級: 低 SHORT
10.地址表達式:
地址表達式是計算存儲器單元地址的表達式,它可由標號、變量名和由方括號"[]"括起來的基址或變址寄存器組成;其計算結果表示一個存儲器單元的地址,而不是該存儲器單元中的值;
注意:彙編語言中,對地址數值的運算都是以字節為單位的,而不是以數據類型的大小為單位的;例如:
W1 DW 1234H,5678H
則,地址表達式W1+1處的內存單元中的數據是7812H,而不是5678H;W1+1表示W1為起始地址,其下一個字節單元的地址,W1+2表示從地址W1出開始,其後2個字節單元地址;
11.符號定義語句:
在程序中,經常會用到一些常數或數值表達式,並把它們直接寫在指令值,當時當需要修改的時候,就要對它們逐一進行修改,這無疑就增加了維護程序的工作量,而且每個常量或表達式所代表的含義也容易忘記;於是,彙編語言提供了為常量或表達式定義一個符號名的方法;一旦定義了符號名,在指令中就可以直接使用它們了;這個功能就類似於C語言中使用宏定義指令#define定義常量的功能相似,也與C++中使用const關鍵字定義常量的功能相似;
(1).等價語句EQU
一般格式:
符號名 EQU 表達式
作用:左邊的符號名代表右邊的表達式;
注意:等價語句不會給符號名分配存儲空間,符號名不能與其它符號名重名,即:符號名必須唯一;符號名也不能被重新定義;程序中凡是出現"表達式"的地方,都使用"符號名"來替換;
(2).使用符號名代表常量或表達式
把一個常量或表達式定義成一個具有一定含義的符號名之後,在程序中就可以用該符號名來代表該常量或表達式;例如:
NUMBER EQU 100 ;給緩衝區的長度取一個符號名
BUFF_LEN EQU NUMBER+2
CR EQU 13 ;給"回車"符的ASCII碼定義一個符號名
LN EQU 10 ;給"換行"符的ASCII碼定義一個符號名
(3).用符號名代表字符串
例如:
GREETING EQU 'How are you!'
(4).用符號名代表關鍵字或指令碼
例如:
MOVE EQU MOV ;給指令碼MOV取另外一個符號名MOVE
COUNTER EQU CX ;給寄存器CX取一個叫做"計數器"的符號名
12.等號語句
彙編語言提供了使用等號"="來定義符號常數的方法,即:可用符號名代表一個常數;一般格式如下:
符號名 = 表達式
數值表達式在彙編時應該可以計算出值,它不能含有向前引用的符號名稱;用等號語句定義的符號名可以被重新定義;可把等號語句看成是高級語言中的一個賦值語句,可以被多次賦值,這一點是與EQU不同的地方;例如:
ABC = 10 + 200*5 ;ABC的值為1010
ABC1 = 5*ABC + 21 ;ABC1的值為5071
COUNT = 1 ;COUNT的值為1
COUNT = 2*COUNT + 1 ;COUNT的值為3
注意:偽指令"="和偽指令"EQU"定義符號名時,凡是在程序中出現符號名的地方,都是用右邊的常量或表達式來替代;
13.標號定義語句
該語句定義一個指定的符號名,該符號名的段地址和偏移地址與下面緊跟存儲單元的相應屬性相同,但是,該符號名的類型是新指定的;
LABEL語句的一般格式如下:
符號名 LABEL 數據類型
常用的數據類型有:BYTE、WORD、DWORD、結構類型、記錄類型、NEAR、FAR;
其中,前五中類型是變量的類型,後面兩種類型是標號的類型;如果格式中的"數據類型"是前面五種類型之一的話,"符號名"就是變量名;如果格式中的"數據類型"是後面的兩種類型之一的話,"符號名"就是標號名;變量名和標號名都具有段地址和偏移地址的屬性;
例如:
WBUFFER LABEL WORD
BUFFER DB 200 DUP(0)
這個LABEL定義語句中,WBUFFER與BUFFER具有完全相同的段地址和偏移地址,但是它們的數據類型不同,目的就是為了使用兩種不同類型的操作來訪問同一塊內存區;
注意:偽指令本身不佔用內存空間;
彙編語言的指令格式,目前有兩種不同的標準:Windows下的彙編語言基本上都遵循Intel風格的語法,比如:MASM、NASM;而Unix/Linux下的彙編語言基本上都遵循AT&T風格的語法;
一、彙編語言語句的通用格式
[名稱[:]] 指令碼 [第一操作數][,第二操作數] ;注釋
彙編語言的指令碼的操作數的個數可以是0、1、2個;當操作數的個數為2的時候,語句還有兩種不同的格式:
Windows下Intel風格的彙編語言語句格式為:
[名稱[:]] 指令碼 目的操作數DST,源操作數SRC ;注釋
Unix/Linux下AT&T風格的彙編語言語句格式為:
[名稱[:]] 指令碼 源操作數SRC,目的操作數DST ;注釋
例如: CYCLE: ADD AX,02H ;(AX)彙編語言語句格式中的"名稱"並不是所有語句都必需的,但是,如果語句中帶有"名稱",那麼,大多數情況下,"名稱"都表示的是內存中某一存儲單元的地址,也就是"名稱"後面各項在內存中存放的第一個存儲單元的地址(包括該"名稱"所在段的段地址和段內偏移地址);比如上面的指令中,CYCLE就是該語句的名稱,CYCLE表示的就是其後面的機器指令碼在內存中存放的第一個地址;"名稱"與指令碼之間的分隔符可以是冒號":",也可以是空格字符" ";當以冒號分割時,該名稱代表的是一個標號;當以空格分割時,該名稱代表的可能是標號,也可能是變量;當指令碼有多個操作數的時候,相鄰兩個操作數之間要用逗號","分割;指令碼與操作數之間必須以空格分割;彙編語言語句的注釋必須以分號";"開頭;
二、組成語句的元素
1、常數:
彙編語言中的常數有整數、字符串;二進位、八進位、十進位、十六進位;彙編語言採用不同的後綴區分:
B:二進位數; O:八進位數; D:十進位數; H:十六進位數;
當一個數值後面沒有後綴的時候,默認為十進位數;
字符串常數是用一對單引號('')括起來的一串字符;
2、表達式:
由操作數和操作符組成;
算數運算操作符: +、-、*、/、MOD,等;取模運算MOD是取兩數相除的餘數;
邏輯運算操作符: AND(邏輯與)、OR(邏輯或)、NOT(邏輯非)、XOR(邏輯異或);
注意:邏輯運算符同時又可以是邏輯運算指令的指令碼,只有當它們出現在指令的操作數部分時,才是操作符;例如:
ADD AL,0CH ADD 0FH ;第一個ADD是指令碼,第二個ADD是操作符;
關係運算操作符: EQ(相等)、NE(不等)、LT(小於)、GT(大於)、LE(小於等於)、GE(大於等於);
彙編語言中的表達式不能單獨構成語句,只能是語句的組成部分;
注意:語句中表達式的求值不是在語句執行時完成的,而是在對源程序進行彙編連結時完成的.所以,語句中各表達式的值必須在彙編或連結時就是確定的,也就是說,表達式中各標識符的值在彙編或連結時就應該是確定的;
3、標號:
標號是由標識符表示的指令的名稱,用於指示對應指令的位置(地址);
標號具有三個屬性:段地址、偏移地址和類型;
標號的段地址和偏移地址屬性是指該標號所對應的指令所在段的段地址和段內偏移地址;
標號的類型有兩種:NEAR和FAR;標號定義成NEAR類型,表示該標號在段內使用,而定義成FAR類型則表示該標號可以在段間使用;
標號的定義:在指令碼前面加上標識符和冒號":";
例如:START: PUSH DS
這條語句裡面,START就是我們定義的標號,它代表指令PUSH的地址,所以,標號可以作為程序轉移指令的操作數(即:要轉向的地址);標號還可以採用偽指令來定義;例如:用LABEL偽指令和過程定義偽指令來定義;
4、變量:
與高級語言一樣,並不是所有的操作數都是常數,彙編語言也有自己的變量,變量的值在程序運行期間是可以被改變的;
A.定義變量:彙編語言中,變量的定義是通過偽指令來完成的;定義變量的偽指令格式如下:
變量名 DB 表達式 ;定義字節變量,又稱單字節變量(1個連續字節),DB-->BYTE
變量名 DW 表達式 ;定義字變量,又稱雙字節變量(2個連續字節),DW-->WORD
變量名 DD 表達式 ;定義雙字變量,又稱四字節變量(4個連續字節),DD-->DWORD
變量名 DF 表達式 ;定義六字節變量,又稱六字節變量(6個連續字節),DF-->FWORD
變量名 DQ 表達式 ;定義長字變量,又稱八字節變量(8個連續字節),DQ-->QWORD
變量名 DT 表達式 ;定義十字節變量(10個連續字節),DT-->TBYTE;
其中,變量名是一個合法的標識符,變量名後面不能加冒號":",只能用空格;變量名不是必要的,可有可無;變量的類型由關鍵字DB、DW、DD、DQ、DT來定義;
變量定義語句中的"表達式"是用於對變量進行初始化的,可有一下幾種情況:
(1).一個或多個常數或表達式;當為多個常數或表達式時,期間要用逗號隔開;如DATA1--DATA4;
(2).帶單引號的字符串;
對於字節型(DB)變量,每個變量的大小為1個字節,每個變量的值不能超過1個字符,每個字節內存入一個字符的ASCII碼值,整個字符串可以在同一對單引號內給出,這相當於是定義了一個字符數組,如DATA5;
對於字類型(DW)變量,每個變量的大小為2個字節,每個變量的值不能超過2個字符,若為2個字符時,同樣遵循高位存入高字節,低位存入低字節的規則;若為1個字符,則該字符的ASCII碼值存入到低字節,高字節為00,如DATA6;
對於雙字類型(DD)變量,每個變量的大小為4個字節,每個變量的值不能超過2個字符,若為2個字符,同樣遵循高位存入高字節,低位存入低字節的規則;但是2個字符的值被存入到雙字變量的最低2個字節中,1個字符的值被存入到雙字變量的最低1個字節中;
對於長字類型(DQ)變量,每個變量的大小為8個字節,每個變量的值不能超過2個字符,若為2個字符,同樣遵循高位存入高字節,低位存入低字節的規則;但是2個字符的值被存入到長字變量的最低2個字節中,1個字符的值被存入到長字變量的最低1個字節中;
(3).一個問號"?",表示該變量的值不確定,即:該變量所表示的內存單元中的內容是不確定的,或者說是,當表達式為問號時,變量所對應的內存區中並沒有存入新的值,而只是預留出了相應的存儲空間;如DATA7、DATA8
(4).重複方式;此時的格式為: 重複次數 DUP(表達式);重複方式指出表達式的值可以重複地存儲到變量對應的內存區中,重複的次數由偽指令給出,相當於定義數組;如DATA9、DATA10
定義變量的例子:
DATA1 DB 20H ;1位元組變量
DATA2 DW 0204H,1000H ;2位元組變量
DATA3 DB (-1*3),(15/3) ;1位元組變量
DATA4 DD 123456H ;4位元組變量
DATA5 DB '0123' ;字符串變量,相當於一個字符數組
DATA6 DW 'AB','C','D' ;字符串變量,相當於一個字符串數組;
DATA7 DB ? ;1位元組變量,未初始化
DATA8 DD ? ;4位元組變量,未初始化
DATA9 DB 5 DUP(0) ;1位元組變量,用5個0初始化,相當於是一個具有5個DB型元素的數組
DATA10 DW 3 DUP(?) ;2位元組變量,未初始化,相當於是一個具有3個DW型元素的數組
變量定義語句中偽指令的功能是在變量名所對應的地址開始的內存區依次存入表達式中的各項值,表達式中的每項值所佔用內存字節數與變量的類型對應;
總結:一個變量的變量名實際上就代表了該變量所對應的內存區在內存段中的有效地址(偏移地址);高地址是指地址值相對較大,低地址是指地址值相對較小,高地址與低地址是相對而言的;
5、變量的屬性:
(1).屬性介紹
一個變量具有一下屬性:
A.段地址(SEG):變量所在段的段地址;
B.偏移地址(OFFSET):變量所在段內的偏移地址;
C.類型(TYPE):變量的類型定義了每個變量所佔用的內存字節數,對於DB、DW、DD、DQ、DT類型定義的變量所佔用的內存字節數分別是1、2、4、8、10;通常又將DB、DW、DD類型所定義的變量分別成為BYTE類型、WORD類型、DWORD類型變量;
常用標識符的類型值列表:
標識符種類 字節變量 字變量 雙字變量 近標號NEAR 遠標號FAR
TYPE的值 1 2 4 -1 -2
D.長度(LENGTH):變量定義時,一個變量名所定義的變量個數;在含有DUP操作符的變量定義中,變量名所定義的變量個數為定義格式中的重複次數;在其它各種變量定義中,每個變量名所定義的變量個數均為1個;
E.大小(SIZE):變量定義語句中,分配給同一個變量名的所有變量的總的字節數,其值為該變量的類型與長度的成績;
其中,段地址、偏移地址和類型屬性是變量的主屬性,而長度和大小屬性是變量的輔助屬性;
(2).屬性操作符:
操作符 表達式 含義
SEG SEG 變量名或標號 取出變量名或標號所在段的段地址
OFFSET OFFSET 變量名或標號 取出變量名或標號所在段內的偏移地址
TYPE TYPE 變量名或標號 取出變量名或標號的類型(變量所佔用的字節數)
LENGTH LENGTH 變量名 取出變量的長度
SIZE SIZE 變量名 取出變量的大小
這些操作符不能單獨構成語句,只能作為表達式的組成部分,並且表達式的求值也是在彙編過程中完成的;
6.強制類型轉換操作符PTR
格式:數據類型 PTR 地址表達式
格式中的"數據類型"可以是BYTE、WORD、DWORD、NEAR、FAR;前三種類型是變量的類型,後兩種類型是標號的類型;格式中的表達式可以是變量、標號、其它地址表達式;
PTR操作符的功能是用來重新定義已定義的變量或標號的類型,其作用域只在當前語句中; 例如:
DATA1 DW 02H
MOV BYTE PTR DATA1,AL
這條指令中,是把DATA1的類型轉換為BYTE類型,然後把AL中的內容存放到DATA1的最低一個字節中;作用域只在這條MOV語句中,過了這條語句,DATA1仍然是DW類型,即:DATA1原來的類型並沒有被修改;
7、複合數據類型:
符合數據類型,除了用DUP定義的重複數據類型之外,與C/C++語言一樣,彙編語言中也有結構體類型、聯合類型、記錄類型;
(1).結構體類型:
A.類型定義格式:
結構類型名 STRUC [對齊類型Alignment][,NONUNIQUE]
Field1 Type1 Exp1
Field2 Type2 Exp2
.
FieldN TypeN ExpN
結構類型名 ENDS
說明:結構體中的欄位名可有可無;若有欄位名,則欄位名必須唯一,每個欄位可獨立存取;若沒有欄位名,則通過偏移量來存取;
對齊方式Alignment:定義每個欄位的字節對齊邊界,對齊值有1、2、4、8、16位元組對齊,值必須是2的冪次方;對齊類似於C/C++中結構體欄位的對齊;
NONUNIQUE:要求結構體中的欄位必須用全名才能訪問;
結構體中的欄位可以有欄位名,也可以沒有欄位名;有欄位名的欄位可直接使用該欄位名來訪問,沒有欄位名的欄位可用使用該欄位在結構體中的偏移量來訪問;
例如:
PERSON STRUC
NO DD ? ;有名欄位,偏移量為0
NAME DB 10 DUP(?) ;有名欄位,偏移量為4
DB 1 ;無名欄位,偏移量為14
PERSON ENDS
B.結構類型變量的定義:
[變量名] 結構類型名
欄位值列表中的各個欄位之間用逗號","分割,欄位值的排列順序及類型應該與該結構定義時說明的各個欄位相一致;如果結構變量中某個欄位的值使用定義結構時說明的預設值,那麼可用逗號來表示;如果所有欄位都使用定義結構體時說明的各個欄位的預設值,則可省去欄位值列表,只需保留一對尖括號""即可;
例如:
Per1 PERSON ;所有欄位都是用默認值
Per2 PERSON ;所有欄位都重新初始化
Per3 PERSON ;第二個欄位使用默認值;
C.結構體類型欄位的引用:
格式: 結構變量名.欄位名
這種引用方式與高級語言中的引用方式完全一致;另外,還可以使用偏移量來訪問某個欄位;
方式1:使用欄位名直接引用
MOV AX,Per3.NAME
方式2:使用欄位的在結構體中的偏移量來引用
LEA SI,Per3 ;取變量Per3對應內存塊的有效地址
MOV AX,[SI+4] ;寄存器相對尋址,4是欄位NAME的偏移量
(2).聯合體類型:
A.類型定義格式:
[聯合體類型名] UNION [對齊方式Alignment][,NONUNIQUE]
Field1 Type1 Exp1
Field2 Type2 Exp2
.
FieldN TypeN ExpN
[聯合體類型名] ENDS
說明:聯合體類型中的各個欄位相互覆蓋,即:同樣的存儲單元被多個不同類型的欄位所對應,並且每個欄位在聯合體類型中的偏移量都是0;聯合體類型所佔用的字節數是其所有欄位所佔字節數的最大值,即:聯合體所佔用的字節數是這個聯合體的所有欄位中佔用字節數最多的那個欄位佔用的字節數;
對齊方式Alignment:可用1、2、4、8、16個字節來指定聯合體中各個欄位字節的對齊邊界,其預設的對齊邊界是1位元組;還可用使用偽指令ALIGN或EVEN來重新定界,也可使用命令行選項/Zp來定界;
NONUNIQUE:要求聯合體類型中的欄位必須使用全名才能訪問;
例如:
DATE UNION
YEAR DB 2010
MONTH DB 07
DAY DB 04
DATE ENDS
B.聯合體類型變量的定義:
聯合體類型的變量只能使用第一個欄位的數據類型來進行初始化;例如:
DATE1 DATE ;定義一個聯合體類型變量DATE1,並使用第一個欄位的數據類型進行初始化
DATE2 DATE ;初始化錯誤,只能使用第一個欄位的數據類型進行初始化;
C.聯合體類型欄位的引用:
格式: 聯合體類型變量名.欄位名
例如:
MOV DATE1.YEAR,2012 ;給聯合體類型變量欄位賦值
MOV AL,DATE1.MONTH ;AL=07
MOV BX,DATE1.YEAR ;BX=2012
MOV DATE1.MONTH,08 ;月份置為8月
(3).記錄類型:
A.類型定義格式:
彙編語言中的記錄類型與高級語言中的記錄類型不同,在彙編語言中,記錄類型是為按照二進位位存取數據提供方便的;記錄類型的說明要用到另一個關鍵字RECORD,格式如下:
記錄名 RECORD 欄位[,欄位,...]
其中,"欄位"代表: 欄位名:寬度[=初始值表達式]
說明:記錄名代表該記錄類型;記錄類型可以由多個欄位組成,相鄰兩個欄位之間用逗號隔開;記錄類型中欄位的屬性包括欄位名、寬度和初始值;記錄類型中,欄位的"寬度"屬性表示該欄位所佔用的二進位位數,它必須是一個常數,並且所有欄位的寬度之和不能大於16(即:有欄位的寬度之和大於8,則系統會自動為該記錄類型分配2位元組的空間,否則只分配1個字節的空間;記錄類型的最後一個欄位排在所分配空間的最低位,然後對記錄中的欄位依次"從右向左"分配二進位位,左邊沒有分完的二進位位自動補0;初值表達式給出的是該欄位的預設值,如果初值超過了該欄位所表示的範圍,那麼,在彙編時將產生錯誤提示信息,如果某欄位沒有初值表達式,則其初值為0;
例1:
COLOR RECORD BLINK:1,BACK:3=0,INTENSE:1=1,FORE:3
該COLOR類型的二進位位分布如下圖所示:
該類型的各個欄位寬度為:1、3、1、3,所以,該記錄佔用8個二進位位,系統為它分配1個字節;
例2:
FLOAT RECORD DSIGN:1,DATA:8,ESIGN:1,EXP:4
該FLOAT類型的二進位位分布如下圖所示:
該類型的總寬度是14個二進位位,所以,系統為它分配2個字節的空間;
B.記錄類型變量的定義:
[變量名] 記錄類型名
說明:變量名就是該記錄類型的變量名,它可預設,則不能使用符號名來訪問該內存單元;欄位值列表是用於給各個欄位賦初值,相鄰兩個欄位值之間用逗號","隔開,其欄位值的排列順序及大小應該按照記錄類型定義時說明的各個欄位的順序和大小來排列;如果記錄類型變量的某個欄位使用默認值,那麼,可用逗號來表示,如果所有欄位都是用默認值,則可省去欄位值列表,但必須保留一對尖括號"";
例如:
COLOR1 COLOR , ,
FLOAT1 FLOAT ,
C.記錄類型欄位的引用:
格式: 記錄類型變量名.欄位名
例如: MOV AL,COLOR1.FORE
D.記錄類型的專用操作符:
操作符WIDTH和MASK是專用於記錄類型的操作符,利用它們可用得到記錄類型的不同屬性;
WIDTH:用於返回記錄或其欄位的二進位位數,即:記錄類型或記錄類型欄位的寬度;書寫格式如下:
WIDTH 記錄名 或 WIDTH 記錄欄位名
例如:記錄類型COLOR,那麼,WIDTH COLOR的值為8,WIDTH BACK的值為3,WIDTH BLINK的值為1;
MASK:它返回一個8位或16位的二進位數,在該二進位數中,被指定記錄或欄位使用的對應位的值為1,否則,其值為0;書寫格式如下:
MASK 記錄名 或 記錄欄位名
例如:記錄類型FLOAT,那麼,MASK EXP的值為000FH,MASK DATA的值為1FE0H,MASK DSIGN的值為2000H;
記錄欄位:記錄欄位名是一個特殊的操作符,它本身也是一個操作數,其返回值是該欄位移到該欄位所在記錄的最低位所需要的位數,即:該欄位最低位在記錄中的位置;
例如:記錄類型FLOAT,則有:
MOV CL,EXP 相當於 MOV CL,0
MOV CL,DATA 相當於 MOV CL,5
(4).類型重定義:
已知某一數據類型,程式設計師可以定義這個數據類型的別名或指針類型.表達這種定義的偽指令是TYPEDEF,其定義形式如下:
新數據類型名 TYPEDEF [位距][PTR] 已知數據類型
其中,"位距"是NEAR、FAR、PROC等;
例如:
CHAR TYPEDEF BYTE ;給BYTE類型定義另外一個別名CHAR,C++中就是: typedef BYTE CHAR
PCHAR TYPEDEF PTR CHAR ;定義一個字符指針數據類型PCHAR,C++中就是:typedef PTR CHAR PCHAR,即:typedef char* PCHAR
那麼,下面的變量定義就是合法的了:
CH1 CHAR 'ABCDEF' ;定義一個字符串常量
PCH1 PCHAR CH1 ;定義一個指向字符串常量CH1的變量
這個功能類似於C++語言中的typedef語句;
8.表達式中的操作符:
HIGH(高8位)、LOW(低8位)
SEG(段地址)、OFFSET(偏移量)、TYPE(數據類型)、LENGTH(變量長度)、SIZE(變量容量)
WIDTH(記錄/記錄欄位的寬度)、MASK(記錄/記錄欄位的屏蔽位),等等;
其中,HIGH和LOW分別用於選取表達式計算結果的高8位和低8位,使用格式如下:
HIGH 表達式 LOW 表達式
9.運算符和操作符的優先級:
優先級: 高 LENGTH、SIZE、WIDTH、MASK、()、[]、.(用於結構欄位)、(用於記錄類型)
↓ PTR、SEG、OFFSET、TYPE、THIS、:(用於段超越前綴)
*、/、MOD、SHL、SHR
↓ HIGH、LOW
+、-
↓ EQ、NE、LT、LE、GT、GE
NOT
↓ AND
OR、XOR
優先級: 低 SHORT
10.地址表達式:
地址表達式是計算存儲器單元地址的表達式,它可由標號、變量名和由方括號"[]"括起來的基址或變址寄存器組成;其計算結果表示一個存儲器單元的地址,而不是該存儲器單元中的值;
注意:彙編語言中,對地址數值的運算都是以字節為單位的,而不是以數據類型的大小為單位的;例如:
W1 DW 1234H,5678H
則,地址表達式W1+1處的內存單元中的數據是7812H,而不是5678H;W1+1表示W1為起始地址,其下一個字節單元的地址,W1+2表示從地址W1出開始,其後2個字節單元地址;
11.符號定義語句:
在程序中,經常會用到一些常數或數值表達式,並把它們直接寫在指令值,當時當需要修改的時候,就要對它們逐一進行修改,這無疑就增加了維護程序的工作量,而且每個常量或表達式所代表的含義也容易忘記;於是,彙編語言提供了為常量或表達式定義一個符號名的方法;一旦定義了符號名,在指令中就可以直接使用它們了;這個功能就類似於C語言中使用宏定義指令#define定義常量的功能相似,也與C++中使用const關鍵字定義常量的功能相似;
(1).等價語句EQU
一般格式:
符號名 EQU 表達式
作用:左邊的符號名代表右邊的表達式;
注意:等價語句不會給符號名分配存儲空間,符號名不能與其它符號名重名,即:符號名必須唯一;符號名也不能被重新定義;程序中凡是出現"表達式"的地方,都使用"符號名"來替換;
(2).使用符號名代表常量或表達式
把一個常量或表達式定義成一個具有一定含義的符號名之後,在程序中就可以用該符號名來代表該常量或表達式;例如:
NUMBER EQU 100 ;給緩衝區的長度取一個符號名
BUFF_LEN EQU NUMBER+2
CR EQU 13 ;給"回車"符的ASCII碼定義一個符號名
LN EQU 10 ;給"換行"符的ASCII碼定義一個符號名
(3).用符號名代表字符串
例如:
GREETING EQU 'How are you!'
(4).用符號名代表關鍵字或指令碼
例如:
MOVE EQU MOV ;給指令碼MOV取另外一個符號名MOVE
COUNTER EQU CX ;給寄存器CX取一個叫做"計數器"的符號名
12.等號語句
彙編語言提供了使用等號"="來定義符號常數的方法,即:可用符號名代表一個常數;一般格式如下:
符號名 = 表達式
數值表達式在彙編時應該可以計算出值,它不能含有向前引用的符號名稱;用等號語句定義的符號名可以被重新定義;可把等號語句看成是高級語言中的一個賦值語句,可以被多次賦值,這一點是與EQU不同的地方;例如:
ABC = 10 + 200*5 ;ABC的值為1010
ABC1 = 5*ABC + 21 ;ABC1的值為5071
COUNT = 1 ;COUNT的值為1
COUNT = 2*COUNT + 1 ;COUNT的值為3
注意:偽指令"="和偽指令"EQU"定義符號名時,凡是在程序中出現符號名的地方,都是用右邊的常量或表達式來替代;
13.標號定義語句
該語句定義一個指定的符號名,該符號名的段地址和偏移地址與下面緊跟存儲單元的相應屬性相同,但是,該符號名的類型是新指定的;
LABEL語句的一般格式如下:
符號名 LABEL 數據類型
常用的數據類型有:BYTE、WORD、DWORD、結構類型、記錄類型、NEAR、FAR;
其中,前五中類型是變量的類型,後面兩種類型是標號的類型;如果格式中的"數據類型"是前面五種類型之一的話,"符號名"就是變量名;如果格式中的"數據類型"是後面的兩種類型之一的話,"符號名"就是標號名;變量名和標號名都具有段地址和偏移地址的屬性;
例如:
WBUFFER LABEL WORD
BUFFER DB 200 DUP(0)
這個LABEL定義語句中,WBUFFER與BUFFER具有完全相同的段地址和偏移地址,但是它們的數據類型不同,目的就是為了使用兩種不同類型的操作來訪問同一塊內存區;
注意:偽指令本身不佔用內存空間;