第4章 巨集處理器
內容 4.1節將會介紹巨集處理器的基本概念 4.2節討論巨集處理器的延伸功能 4.3節將會介紹一些巨集處理器的設計選項 4.4節將簡短地介紹三個巨集處理器的實際範
4.1 巨集處理器的基本功能 4.1.1節:討論巨集處理器的定義、呼叫(invocation)、展開(expansion)和參數替換 4.1.2節:介紹簡單巨集處理器的單階單演算法以及巨集處理上所需的資料結構
4.1.1巨集的定義和展開 圖4.1的程式是巨集處理器的輸入 圖4.2所示程 二個巨集指令:RDBUFF和WRBUFF MACRO指引(directive)之後的是構成巨集之主體(body)的敘述(第15行到90行)。這些敘述會在巨集展開時產生出來 MEND指引(第95行)可以標示巨集定義的結尾。
圖4.1 SIC/XE程式語言中的巨集
圖4.2 圖4.1巨集展開後的程式
注意 每ㄧ次呼叫巨集時,就會產生(和組譯)巨集所展開的敘述。但是,不論呼叫副程式多少次,副程式中的敘述都只會顯示一次。 巨集的主體中並沒有標籤
4.1.2 巨集處理器的演算法和資料結構 二階段巨集處理器 此種二階段巨集處理器並不允許巨集指令的主體中,具有其他巨集的定義 第一階段處理所有的巨集定義 第二次階段處理所有的巨集呼叫敘述的展開 此種二階段巨集處理器並不允許巨集指令的主體中,具有其他巨集的定義 單階段巨集處理器會在巨集定義與巨集展開之間切換,以順利處理一些巨集
巨集處理器有三項主要的資料結構 巨集定義是儲存在「定義資料表」(DEFTAB) 巨集定義的註解行(comment lines)並不會進入到DEFTAB 巨集名稱也會進入到「名稱資料表」(NAMTAB)中,以做為DEFTAB的索引。 「引數資料表」(ARGTAB):主要是用於巨集呼叫的展開時間
圖4.3 巨集主體中巨集定義的範例
圖4-4 針對圖4.1程式之巨集處理器表格的內容
單階段巨集處理器的演算法
4.2 與硬體無關的巨集處理器功能 4.2.1節將討論巨集指令參數和字串的連接方法。 4.2.2節將討論在巨集展開時,產生唯一標籤的方法 4.2.3節是介紹條件巨集展開的重要議題,並且利用一些範例來說明其概念。 4.2.4節所要介紹的是巨集指令的定義,以及關鍵字參數(keyword parameter)的使用。
4.2.1 巨集參數的連接 大部分的巨集處理器都會允許參數與其他字串進行連接。 假設此巨集指令的參數名稱是&ID, 巨集定義的主體中應該包含一個敘述: LDA X&ID1 LDA X&ID→1 &ID參數的結尾可以清楚地指定出來。 圖4.7展示在巨集展開中,裡產生唯一標籤的一個技術。
圖4.6 巨集參數的串接
圖4.7 在巨集展開中產生唯一的標籤
4.2.3 條件式巨集展開 大部分的巨集處理器可以根據在巨集呼叫中的引數,來修改巨集展開所產生的一系列敘述 IF敘述是用來判斷一個布林的運算式,如果此運算式的值為「TRUE」時,則IF後跟的敘述將持續進行巨集展開,直到遭遇到ELSE敘述為止; 否則,將會略過這些敘述,而展開ELSE之後的敘述 ENDIF敘述將會終止由IF敘述啟始的條件式
圖4.8 巨集時間條件式敘述的使用
巨集時間的WHILE迴圈敘述 只要WHILE敘述的條件成立時,就會ㄧ直重複展開WHILE敘述與ENDW敘述之間的敘述。
圖4.9 巨集時間迴圈敘述的使用
4.2.4 關鍵字巨集參數 迄今,所有的巨集指令定義都是使用位置參數(positional parameters)。 「關鍵參數」(Keyword parameters)是另一種不同形式的參數規範,每個引數值都會標示一個關鍵字,來表示對應的引數,因此引數可以用任何的順序來呈現。 GENER TYPE=DIRECT, CHANNEL=3
圖4.10 在巨集指令中使用關鍵字參數
4.3 巨集處理器的設計選項 4.3.1遞迴的巨集展開。 4.3.2節將要討論不受任何特殊程式所束縛的通用巨集處理器 4.3.3節將探討此項議題的其它面向:巨集處理器與一個特殊組譯器或編譯器的整合。
4.3.1遞迴的巨集展開 一個巨集呼叫另一個巨集 當程序以遞迴方式進行呼叫時,編譯器必須確定先前所宣告的變數值已經儲存起來。此外,它同時也要處理程序在回歸時的相關細節
圖4.11 巢狀巨集呼叫的範例
ARGTAB表 Parameter Value 1 F1 2 (unused) . Parameter Value 1 BUFFER 2 LENGTH 3 F1 4 (unused) . Parameter Value 1 F1 2 (unused) .
4.3.2 一般用途的巨集處理器 巨集處理器是最常用於協助組合語言程式;巨集處理器也可以運用在高階語言上 程式語言之間的其它差異,是與程式語言之項目、運算式或敘述的分群機制有關。一般用途的巨集處理器在掃描原始程式時,需要考量這些的分群(groupings)。 另一個較為通常的問題是程式語言的符記(tokens) 巨集定義和巨集呼叫敘述的使用語法
4.3.3 程式語言轉譯器中的巨集處理 巨集處理器可以稱之為「前置處理器」(preprocessors) 另一種方法:整合程式語言的轉譯器以及巨集處理器的功能。 逐行巨集處理器 以避免針對原始程式產生另一階段的處理 結合巨集處理器和語言轉譯器所需的資料結構 整合的巨集處理器 使用語言轉譯器從原始程式中所擷取出來的任何有用資訊 整合性和逐行式的巨集處理器也有其缺點。因為它們需要特別的設計和撰寫,並且必須與特定的組譯器或編譯器協同運作
4.4 實作範例 4.4.1 MASM巨集處理器 4.4.2 ANSI C巨集語言 4.4.3 ELENA 巨集處理器
4.4.1 MASM巨集處理器 MASM的巨集處理器是與組合語言的第一階段整合在一起 支援先前所討論之巨集處理器的所有功能 巨集可能在程式中重新定義 MASM巨集處理器與SIC之間的差異在於條件巨集展開敘述
圖4.12 MASM 巨集和條件敘述的範例 (a) (c) (b) (d)
圖4.13 MASM反覆敘述的範例 (a) (b)
4.4.2 ANSI C巨集語言 巨集的定義和呼叫是由前置處理器來執行 此前置處理器通常並不會與編譯器整合在一起 #define NULL 0 #define EOF (-1) 例如:定義巨集 #define EQ == 一個程式將可以寫成 while (I EQ 0)... 巨集處理器會將此轉換為 while (I == 0)...
範例 #define ABSDIFF(X,Y) ((X) > (Y) ? (X) – (Y) : (Y) – (X)) ABSDIFF(I+1,J-5) 將會被巨集處理器轉換成 ((I+1) > (J-5) ? (I+1) – (J-5) : (J-5) – (I+1)) #define DISPLAY(EXPR) printf("EXPR = %d\n" EXPR) 此巨集呼叫 #DESPLAY(I*J+1) 將會展開成. printf("EXPR = %d\n", I*J+1)
範例 #define DISPLAY(EXPR) printf(#EXPR “= %d\n”, EXPR) DISPLAY(I*J+1) 將會展開成為 printf("I*J+1" "= %d\n", EXPR) DISPLAY(ABSDIFF(3,8)) 會展開成下列的程式 Printf(“ABSDIFF(3,8)” “= %d\n”, ABSDIFF(3,8)) 在重新掃描後,將會變成下列的程式 Printf(“ABSDIFF(3,8)” “= %D\N”,((3) > (8) ? (3) – (8) : *8) – (3)))
範例 #define DEBUG 1 . #if DEBUG == 1 printf(...) /* debugging output */ #endif 在此範例中,printf敘述將會出現在前置處理程式的輸出中 define DEBUG 0 則程式中將不會出現此printf 。下列敘述也可以產生相同的結果 #ifdef DEBUG 在此範例中,假如原始程式是用 #define敘述來定義DEBUG時,則程式中將會出現printf敘述。
4.4.3 ELENA 巨集處理器 ELENA的巨集定義中包括標頭(header)和主體(body) 一系列的關鍵字(keywords)和參數標誌(以特別符號 % 來辨識)。 例如:一個具有標頭的巨集如下列所示: %1 = %2 + %3 可以用下列方式來呼叫: ALPHA = BETA + GAMMA 巨集標頭與巨集呼叫的比對程序會比較複雜。
圖4.14 ELENA巨集定義和呼叫的範例
圖4.15 ELENA巨集時間指令的範例