天天看點

校長講堂第十講

預處理器

運作的程式并不是我們所寫的程式:因為 C 預處理器首先對其進行了轉換。出于兩個主要原因(和很多次要原因),預處理器為我們提供了一些簡化的途徑。

首先,我們希望可以通過改變一個數字并重新編譯程式來改變一個特殊量(如表的大小)的所有執行個體。

其次,我們可能希望定義一些東西,它們看起來象函數但沒有函數調用所需的運作開銷。例如,putchar()和 getchar()通常實作為宏以避免對每一個字元的輸入輸出都要進行函數調用。

6.1 宏不是函數

由于宏可以象函數那樣出現,有些程式員有時就會将它們視為等價的。是以,看下面的定義:

#define max(a, b) ((a) > (b) ? (a) : (b))

注意宏體中所有的括号。它們是為了防止出現 a 和 b 是帶有比>優先級低的表達式的情況。

一個重要的問題是,像 max()這樣定義的宏每個操作數都會出現兩次并且會被求值兩次。是以,在這個例子中,如果 a 比 b 大,則 a 就會被求值兩次:一次是在比較的時候,而另一次是在計算 max()值的

時候。

這不僅是低效的,還會發生錯誤:

biggest = x[0];

i = 1;

while(i < n)

biggest = max(biggest, x[i++]);

當 max()是一個真正的函數時,這會正常地工作,但當 max()是一個宏的時候會失敗。譬如,假設 x[0]是 2、x[1]是 3、x[2]是 1。我們來看看在第一次循環時會發生什麼。指派語句會被擴充為:

biggest = ((biggest) > (x[i++]) ? (biggest) : (x[i++]));

首先,biggest 與 x[i++]進行比較。由于 i 是 1 而 x[1]是 3,這個關系是“假”。其副作用是,i 增長到 2。

由于關系是“假”,x[i++]的值要賦給 biggest。然而,這時的 i 變成 2 了,是以賦給 biggest 的值是 x[2]的值,即 1。

避免這些問題的方法是保證 max()宏的參數沒有副作用:

biggest = x[0];

for(i = 1; i < n; i++)

biggest = max(biggest, x[i]);

還有一個危險的例子是混合宏及其副作用。這是來自 UNIX 第八版的中 putc()宏的定義:

#define putc(x, p) (--(p)->_cnt >= 0 ? (*(p)->_ptr++ = (x)) : _flsbuf(x, p))

putc()的第一個參數是一個要寫入到檔案中的字元,第二個參數是一個指向一個表示檔案的内部資料結構的指針。注意第一個參數完全可以使用如*z++之類的東西,盡管它在宏中兩次出現,但隻會被求值一次。

而第二個參數會被求值兩次(在宏體中,x 出現了兩次,但由于它的兩次出現分别在一個:的兩邊,是以在putc()的一個執行個體中它們之中有且僅有一個被求值)。由于 putc()中的檔案參數可能帶有副作用,這偶爾會出現問題。不過,使用者手冊文檔中提到:“由于 putc()被實作為宏,其對待 stream 可能會具有副作用。特别是 putc(c, *f++)不能正确地工作。”但是 putc(*c++, f)在這個實作中是可以工作的。

有些 C 實作很不小心。例如,沒有人能正确處理 putc(*c++, f)。另一個例子,考慮很多 C 庫中出現的 toupper()函數。它将一個小寫字母轉換為相應的大寫字母,而其它字元不變。如果我們假設所有的小寫字母和所有的大寫字母都是相鄰的(大小寫之間可能有所差距),我們可以得到這樣的函數:

toupper(c) {

if(c >= 'a' && c <= 'z')

c += 'A' - 'a';

return c;

}

在很多 C 實作中,為了減少比實際計算還要多的調用開銷,通常将其實作為宏:

#define toupper(c) ((c) >= 'a' && (c) <= 'z' ? (c) + ('A' - 'a') : (c))

很多時候這确實比函數要快。然而,當你試着寫 toupper(*p++)時,會出現奇怪的結果。

另一個需要注意的地方是使用宏可能會産生巨大的表達式。例如,繼續考慮 max()的定義:

#define max(a, b) ((a) > (b) ? (a) : (b))

假設我們這個定義來查找 a、b、c 和 d 中的最大值。如果我們直接寫:

max(a, max(b, max(c, d)))

它将被擴充為:

((a) > (((b) > (((c) > (d) ? (c) : (d))) ?

(b) : (((c) > (d) ? (c) : (d))))) ?

(a) : (((b) > (((c) > (d) ? (c) : (d))) ?

(b) : (((c) > (d) ? (c) : (d))))))

這出奇的龐大。我們可以通過平衡操作數來使它短一些:

max(max(a, b), max(c, d))

這會得到:

((((a) > (b) ? (a) : (b))) > (((c) > (d) ? (c) : (d))) ? (((a) > (b) ? (a) : (b))) : (((c) > (d) ? (c) : (d))))

這看起來還是寫:

biggest = a;

if(biggest < b) biggest = b;

if(biggest < c) biggest = c;

if(biggest < d) biggest = d;

比較好一些。