• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • C語言:陷阱和缺陷_6

    發表于:2007-05-25來源:作者:點擊數: 標簽:缺陷程序行的處理器陷阱
    6 預處理器 運行的程序并不是我們所寫的程序:因為C預處理器首先對其進行了轉換。出于兩個主要原因(和很多次要原因),預處理器為我們提供了一些簡化的途徑。 首先,我們希望可以通過改變一個數字并重新編譯程序來改變一個特殊量(如表的大?。┑乃袑嵗?
    6 預處理器

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

        首先,我們希望可以通過改變一個數字并重新編譯程序來改變一個特殊量(如表的大?。┑乃袑嵗?sup>腳注[9]。

        其次,我們可能希望定義一些東西,它們看起來象函數但沒有函數調用所需的運行開銷。例如,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第八版的<stdio.h>中putc()宏的定義:

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

    putc()的第一個參數是一個要寫入到文件中的字符,第二個參數是一個指向一個表示文件的內部數據結構的指針。注意第一個參數完全可以使用如*z++之類的東西,盡管它在宏中兩次出現,但只會被求值一次。而第二個參數會被求值兩次(在宏體中,x出現了兩次,但由于 它的兩次出現分別在一個:的兩邊,因此在putc()的一個實例中它們之中有且僅有一個被求值)。由于putc()中的文件參數可能帶有副作用,這偶爾會出現問題。不過,用戶手冊文檔中提到:“由于putc()被實現為宏,其對待stream可能會具有副作用。特別是putc(c, *f++)不能正確地工作?!钡莗utc(*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;

    比較好一些。

    6.2 宏不是類型定義

        宏的一個通常的用途是保證不同地方的多個事物具有相同的類型:

    #define FOOTYPE struct foo
    FOOTYPE a;
    FOOTYPE b, c;

    這允許程序員可以通過只改變程序中的一行就能改變a、b和c的類型,盡管a、b和c可能聲明在很遠的不同地方。

        使用這樣的宏定義還有著可移植性的優勢——所有的C編譯器都支持它。很多C編譯器并不支持另一種方法:

    typedef struct foo FOOTYPE;

    這將FOOTYPE定義為一個與struct foo等價的新類型。

        這兩種為類型命名的方法可以是等價的,但typedef更靈活一些。例如,考慮下面的例子:

    #define T1 struct foo *
    typedef struct foo * T2;

    這兩個定義使得T1和T2都等價于一個struct foo的指針。但看看當我們試圖在一行中聲明多于一個變量的時候會發生什么:

    T1 a, b;
    T2 c, d;

    第一個聲明被擴展為:

    struct foo * a, b;

    這里a被定義為一個結構指針,但b被定義為一個結構(而不是指針)。相反,第二個聲明中c和d都被定義為指向結構的指針,因為T2的行為好像真正的類型一樣。


    腳注
        1. 本文是基于圖書《C Traps and Pitfalls》(Addison-Wesley, 1989, ISBN 0-201-17928-8)的一個擴充,有興趣的讀者可以讀一讀它。
        2. 因為!=的結果不是1就是0。
        3. 感謝Guy Harris為我指出這個問題。
        4. Dennis Ritchie和Steve Johnson同時向我指出了這個問題。
        5. 感謝一位不知名的志愿者提出這個問題。
        6. 感謝Richard Stevens指出了這個問題。
        7. 一些C編譯器要求每個外部對象僅有一個定義,但可以有多個聲明。使用這樣的編譯器時,我們何以很容易地將一個聲明放到一個包含文件中,并將其定義放到其它地方。這意味著每個外部對象的類型將出現兩次,但這比出現多于兩次要好。
        8. 分離函數參數用的逗號不是逗號運算符。例如在f(x, y)中,x和y的獲取順序是未定義的,但在g((x, y))中不是這樣的。其中g只有一個參數。它的值是通過對x進行求值、拋棄這個值、再對y進行求值來確定的。
        9. 預處理器還可以很容易地組織這樣的顯式常量以能夠方便地找到它們。
        10. PDP-11和VAX-11是數組設備集團(DEC)的商標。



    原文轉自:http://www.kjueaiud.com

    評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>