810 likes | 960 Views
單元 10: 結構與巨集. 章節概念. 結構 Structures 巨集 Macros 條件組譯指引 Conditional-Assembly Directives 定義重複的區塊 Defining Repeat Blocks. 結構 – 概念. 定義結構 宣告結構變數 參照結構變數 範例: 顯示系統時間 含有結構的結構 範例: 醉漢漫步 宣告和使用聯合體 . 結構. 是一個樣板或樣式,這種樣板用於將邏輯上相關的變數整理在一起。 . 結構中的各個變數則可以稱為欄位 ( field)
E N D
章節概念 • 結構Structures • 巨集Macros • 條件組譯指引Conditional-Assembly Directives • 定義重複的區塊Defining Repeat Blocks
結構 – 概念 • 定義結構 • 宣告結構變數 • 參照結構變數 • 範例: 顯示系統時間 • 含有結構的結構 • 範例: 醉漢漫步 • 宣告和使用聯合體
結構 • 是一個樣板或樣式,這種樣板用於將邏輯上相關的變數整理在一起。 . • 結構中的各個變數則可以稱為欄位 (field) • 程式中的敘述可以將結構當成單一的實體來加以存取,也可以針對結構的各個欄位進行存取 • 結構通常含有不同資料型別的欄位 • 聯合體也是用於將多個識別字整理成一組,不過,這些識別字會重疊在 記憶體中的相同區域內
使用結構 使用結構,會牽涉到以下三個連續的步驟: • 定義結構。 • 宣告一個或多個該結構型別的變數,這些變數稱為結構變數 (structure variables)。 • 撰寫執行時期的指令來存取結構的各欄位。
定義結構 name STRUCT field-declarations name ENDS • 結構幾乎可以含有任何數量的欄位
COORD結構 • 在 Windows API 中所定義的 COORD 結構,可以識別出螢幕座標 X 和 Y。 COORD STRUCT X WORD ? ; offset 00 Y WORD ? ; offset 02 COORD ENDS
Employee 結構 以下的結構定義必須出現在,Employee 型別的任何變數被宣告之前: • Employee STRUCT • IdNum BYTE "000000000" • LastName BYTE 30 DUP(0) • Years WORD 0 • SalaryHistory DWORD 0,0,0,0 • Employee ENDS
宣告結構變數 • 在宣告結構變數的時候,可以選擇使用明確的值,對此變數進行初始化的工作。 • 在此宣告語法中,識別字的製訂方式與 MASM 中的其他變數名稱製訂方式,依循著相 同的法則。 • 在宣告結構變數時,如果使用了空的角括號 < >,那麼在所產生的結構變數中,相對應的 欄位值會採用結構定義中預設的欄位值。 • 範例: .data point1 COORD <5,10> point2 COORD <> worker Employee <>
陣列初始化 • 使用 DUP 運算子,將陣列中的部分或全部元素初始化。 .data emp Employee <,,,2 DUP(20000)>
結構陣列 • 以使用 DUP 運算子,來建立結構陣列 (array of structures)。 • 在下列敘述 中,AllPoints 中每個元素的 X 和 Y 欄位,都會初始化成零: NumPoints = 3 AllPoints COORD NumPoints DUP(<0,0>) RD_Dept Employee 20 DUP(<>) accounting Employee 10 DUP(<,,,4 DUP(20000) >)
參照結構變數 • Employee STRUCT ; bytes • IdNum BYTE "000000000" ; 9 • LastName BYTE 30 DUP(0) ; 30 • Years WORD 0 ; 2 • SalaryHistory DWORD 0,0,0,0 ; 16 • Employee ENDS ; 57 .data worker Employee <> mov eax,TYPE Employee ; 57 mov eax,SIZEOF Employee ; 57 mov eax,SIZEOF worker ; 57 mov eax,TYPE Employee.SalaryHistory ; 4 mov eax,LENGTHOF Employee.SalaryHistory ; 4 mov eax,SIZEOF Employee.SalaryHistory ; 16
參照結構變數(續前一頁) mov dx,worker.Years mov worker.SalaryHistory,20000 ; first salary mov worker.SalaryHistory+4,30000 ; second salary mov edx,OFFSET worker.LastName mov esi,OFFSET worker mov ax,(Employee PTR [esi]).Years mov ax,[esi].Years ; invalid operand (ambiguous)
通過陣列的迴圈 在使用間接或索引定址方式的時候,可以搭配迴圈來操作結構陣列。 下列程式 (AllPoints.asm) 會指定座標值給 AllPoints 陣列: .data NumPoints = 3 AllPoints COORD NumPoints DUP(<0,0>) .code mov edi,0 ; array index mov ecx,NumPoints ; loop counter mov ax,1 ; starting X, Y values L1: mov (COORD PTR AllPoints[edi]).X,ax mov (COORD PTR AllPoints[edi]).Y,ax add edi,TYPE COORD inc ax Loop L1
範例: 顯示系統時間(3之1) • MS-Windows 有提供能設定螢幕游標位置,以及獲取系統時間的主控台函式。 • 使用 COORD and SYSTEMTIME 結構: • SYSTEMTIME STRUCT • wYear WORD ? • wMonth WORD ? • wDayOfWeek WORD ? • wDay WORD ? • wHour WORD ? • wMinute WORD ? • wSecond WORD ? • wMilliseconds WORD ? • SYSTEMTIME ENDS
範例: 顯示系統時間(3之2) • GetStdHandle擷取標準主控台輸出處置碼 • SetConsoleCursorPosition要設定游標位置 • GetLocalTime取得系統時間 .data sysTime SYSTEMTIME <> XYPos COORD <10,5> consoleHandle DWORD ? .code INVOKE GetStdHandle, STD_OUTPUT_HANDLE mov consoleHandle,eax INVOKE SetConsoleCursorPosition, consoleHandle, XYPos INVOKE GetLocalTime, ADDR sysTime
範例: 顯示系統時間(3之3) • 顯示使用程式庫呼叫的時間: mov edx,OFFSET TheTimeIs ; "The time is " call WriteString movzx eax,sysTime.wHour ; hours call WriteDec mov edx,offset colonStr ; ":" call WriteString movzx eax,sysTime.wMinute ; minutes call WriteDec mov edx,offset colonStr ; ":" call WriteString movzx eax,sysTime.wSecond ; seconds call WriteDec
含有結構的結構(2之1) • 結構可以含有其他結構的實體。 • Rectangle 能利用其左上角和右下角兩個點加以定 義,而這兩個點為 COORD 結構: COORD STRUCT X WORD ? Y WORD ? COORD ENDS Rectangle STRUCT UpperLeft COORD <> LowerRight COORD <> Rectangle ENDS .data rect1 Rectangle { {10,10}, {50,20} } rect2 Rectangle < <10,10>, <50,20> >
含有結構的結構(2之2) • 敘述直接參照了巢狀結構的欄位 • 以使用間接運算元的方式,來存取巢狀結構的欄位。 mov rect1.UpperLeft.X, 10 mov esi,OFFSET rect1 mov (Rectangle PTR [esi]).UpperLeft.Y, 10 // use the OFFSET operator mov edi,OFFSET rect2.LowerRight mov (COORD PTR [edi]).X, 50 mov edi,OFFSET rect2.LowerRight.X mov WORD PTR [edi], 50
範例: 醉漢漫步 • 隨機路徑模擬 • 當模擬執行動作時,使用nested結構累積路徑資料 • 使用多個部門結構選擇方向 WalkMax = 50 DrunkardWalk STRUCT path COORD WalkMax DUP(<0,0>) pathsUsed WORD 0 DrunkardWalk ENDS
宣告和使用聯合體 • 在結構中的每個欄位的位移值,都是相對於結構的第一個位元組,相形之下,在聯合體 (union) 中的所有欄位則都是以相同的位移作為起始點。 • 聯合體所佔的儲存空間 大小,是等於該聯合體內最長欄位的長度。 • 當聯合體不是結構的一部分時,它的宣告方 式會需要使用到 UNION 和 ENDS 指引: unionname UNION union-fields unionname ENDS
範例 Integer 聯合體消耗4 位元組 (相當於最大的欄位) Integer UNION D DWORD 0 W WORD 0 B BYTE 0 Integer ENDS 這種不 一致初始值的情形下,組譯器將忽略掉欄位 W 和 B 被宣告的初始設定子。 宣告並且使用聯合體變數 : .data val1 Integer <12345678h> val2 Integer <100h> val3 Integer <>
範例 The variant field name is required when accessing the union: mov val3.B, al mov ax,val3.W add val3.D, eax
含有聯合體的結構 針對 FileID 欄位所做的那樣,直接在結構裡面宣告一個聯合體 Integer UNION D DWORD 0 W WORD 0 B BYTE 0 Integer ENDS FileInfo STRUCT FileID Integer <> FileName BYTE 64 DUP(?) FileInfo ENDS .data myFile FileInfo <> .code mov myFile.FileID.W, ax
下一章 • 結構 Structures • 巨集 Macros • 條件組譯指引 Conditional-Assembly Directives • 定義重複的區塊 Defining Repeat Blocks
巨集 • 綜述 Introducing Macros • 定義巨集 Defining Macros • 引用巨集 Invoking Macros • 額外的巨集功能 Macro Examples • 巨集函式庫 Nested Macros • 示範程式: Wrappers
綜述 • 巨集程序 (macro procedure) 是指,其有加以命名的組合語言敘述的區塊。 • 一旦加以定義, 便能在程式中,任意多次地引用該巨集程序。 • 當讀者引用 (invoke) 了一個巨集程序時,其程式碼將會被複製,並且直接插入程式中,插入的位置則是引用巨集的地方。 • 習慣上,我 們是稱呼這種引用過程,為呼叫 (calling) 巨集程序,不過就技術上而言,過程中並沒有 牽涉到任何 CALL 指令。
定義巨集 • 定義巨集,需要使用到 MACRO 和 ENDM 指引。其語法如下 : • 在 MACRO 和 ENDN 指引之間的敘述,直到該巨集被引用之前,都不會被組譯。 • 在巨集的定義中,可以 有任何個數的參數,不過各參數之間必須以逗號區隔開。 macroname MACRO [parameter-1, parameter-2,...] statement-list ENDM
mNewLine 巨集範例 這是你如何定義並且喚起一個簡單的巨集 mNewLine MACRO ; define the macro call Crlf ENDM .data .code mNewLine ; invoke the macro 組合器必須以 " 呼叫 crlf" 代替 " mNewLine" 。
mPutChar 巨集 mPutchar 巨集會接收單一個輸入參數,此參數名稱為 char,然後此巨集會呼叫本書連結函式庫中的 WriteChar 函式,將 char 顯示在螢幕上: . mPutchar MACRO char push eax mov al,char call WriteChar pop eax ENDM Definition: .code mPutchar 'A' Invocation: 1 push eax 1 mov al,'A' 1 call WriteChar 1 pop eax Expansion:
引用巨集(2之1) • 經由在程式中插入巨集的名稱,就可以呼叫 ( 引用 ) 該巨集,如果需要的話,也可以緊隨 在巨集名稱之後,加上巨集的引數。 • 巨集名稱必須是在原始碼的該呼叫位置以前,就已經定義過的巨集的名稱。 • 每 個引數都是一個文字值,該文字值將會取代巨集中的參數。 • 引數的順序必須與參 數的順序相對應,但引數的個數並不須要與參數個數保持一致。 • 傳遞的引數太多了, 則組譯器將發出警告。 如果所傳遞的引數太少了,那麼未被填入的參數將處於空白狀態。
引用巨集(2之2) Relationships between macros, arguments, and parameters:
mWriteStr 巨集(2之1) Provides a convenient way to display a string, by passing the string name as an argument. mWriteStr MACRO buffer push edx mov edx,OFFSET buffer call WriteString pop edx ENDM .data str1 BYTE "Welcome!",0 .code mWriteStr str1
mWriteStr巨集(2之2) The expanded code shows how the str1 argument replaced the parameter named buffer: mWriteStr MACRO buffer push edx mov edx,OFFSET buffer call WriteString pop edx ENDM 1 push edx 1 mov edx,OFFSET str1 1 call WriteString 1 pop edx
錯誤引數 • 如果你略過一個錯誤引數,當展開程式碼被組合的時候,會偵測到錯誤。 • 例如: .code mPutchar 1234h 1 push eax 1 mov al,1234h ; error! 1 call WriteChar 1 pop eax
空白引數 • 如果你略過一個空白引數,當展開程式碼被組合時,錯誤也被能偵測到。 • 例如: .code mPutchar 1 push eax 1 mov al, 1 call WriteChar 1 pop eax
額外的巨集功能 • mReadStr -從鍵盤讀取一個字串 • mGotoXY -將游標設定在主控台視窗緩衝區中的指定位置 • mDumpMem -顯示某個範圍的記憶體
mReadStr mReadStr 巨集在 ReadString 程序呼叫的周圍提供一個方便的封裝 mReadStr MACRO varName push ecx push edx mov edx,OFFSET varName mov ecx,(SIZEOF varName) - 1 call ReadString pop edx pop ecx ENDM .data firstName BYTE 30 DUP(?) .code mReadStr firstName
mGotoXY 藉由呼叫 Gotoxy 程式庫的 mGotoXY 巨集控制台游標位置。 mGotoxy MACRO X:REQ, Y:REQ push edx mov dh,Y mov dl,X call Gotoxy pop edx ENDM 緊鄰 X 的 REQ 和 Y 確認他們為必需的參數。
mDumpMem 對連結程式庫的 DumpMem 程序的 mDumpMem 巨集流線呼叫 mDumpMem MACRO address, itemCount, componentSize push ebx push ecx push esi mov esi,address mov ecx,itemCount mov ebx,componentSize call DumpMem pop esi pop ecx pop ebx ENDM
mDump mDump 巨集顯示一個變數,使用它的已知屬性。 如果<useLabel>是非空格,變數的名字被顯示 mDump MACRO varName:REQ, useLabel IFB <varName> EXITM ENDIF call Crlf IFNB <useLabel> mWrite "Variable name: &varName" ELSE mWrite " " ENDIF mDumpMem OFFSET varName, LENGTHOF varName, TYPE varName ENDM
mWrite 巨集通常同時含有程式碼和資料。 舉例而言,下列的 mWrite 巨集用於在控制台上,顯示 一個文數字字串。 mWrite MACRO text LOCAL string .data ;; data segment string BYTE text,0 ;; define local string .code ;; code segment push edx mov edx,OFFSET string call Writestring pop edx ENDM
2 .data 2 ??0002 BYTE "My Sample Macro Program",0 2 .code 2 push edx 2 mov edx,OFFSET ??0002 2 call Writestring 2 pop edx 1 call Crlf nesting level Nested Macros 當一個巨集引用了另一個巨集時,便稱為巢狀巨集 mWriteLn MACRO text mWrite text call Crlf ENDM mWriteLn "My Sample Macro Program"
練習 • 寫一個命名為” mAskForString”的巢狀巨集,清除螢幕,設置游標位置。 • 使用下列的密碼和資料測試你的巨集: .data acctNum BYTE 30 DUP(?) .code main proc mAskForString 5,10,"Input Account Number: ", \ acctNum Solution . . .
. . . 解決方案 mAskForString MACRO row, col, prompt, inbuf call Clrscr mGotoXY col, row mWrite prompt mReadStr inbuf ENDM
示範程式: Wrappers • 讓我們建立一個命名為 Wraps.asm 的簡短程式,這個程式是用於展示,先前已經介紹過 的封裝程序的巨集。 • 因為每個巨集都隱藏了很多繁冗的參數傳遞,所以這個程式真是驚 人地簡潔。 • 這裡將假設,截至目前所討論的巨集都放在 Macros.inc 檔案內:
下一章 • 結構 Structures • 巨集 Macros • 條件組譯指引 Conditional-Assembly Directives • 定義重複的區塊 Defining Repeat Blocks
條件組譯指引 • 檢查遺漏的引數 Checking for Missing Arguments • 預設的引數初始設定子 Default Argument Initializers • 布林運算式 Boolean Expressions • IF、ELSE 和 ENDIF 指引 • IFIDN 和 IFIDNI 指引 • 特殊運算子 Special Operators • 巨集函式 Macro Functions
檢查遺漏的引數 • 巨集引數是空的,那麼這個指引將回傳真 (true) 值。 範例: • IFB <row> ;; if row is blank, • EXITM ;; exit the macro • ENDIF
mWriteString 範例 如果字串參數是空的,在組合期間顯示一個訊息: • mWriteStr MACRO string • IFB <string> • ECHO ----------------------------------------- • ECHO * Error: parameter missing in mWriteStr • ECHO * (no code generated) • ECHO ----------------------------------------- • EXITM • ENDIF • push edx • mov edx,OFFSET string • call WriteString • pop edx • ENDM