C陷阱篇之語法正確語義錯誤的編譯器侷限

    編譯器功能只是語法檢查,只要語法正確,那它就遵循一個原則:程序員總是對的。其實也只能這樣,如果腦子裏想着A,實現的卻是B,而A/B語法上都成立,那編譯器除了認爲你正確,還能做什麼呢?只能我們自己注意區分A/B相似且語法都成立的下列情況。

代碼佈局與縮進的誤導

    計算機從不受代碼語法和佈局影響,而人卻易受眼睛影響做出傾向性判斷,這些判斷有時是錯誤的。如:

    for (i=0; i<max_time; i++)

        LeftElmt = Left[i]; 

        Left[i] = Right[i];   

        Right[i] = LeftElmt;

    這種排列方式容易使人認爲每次循環3條語句全部執行,實際上沒有括號,只有第1條語句執行max_time次,而後兩條語句僅執行一次。這裏for循環與縮進放在一起,對外傳遞了誤導信號,使人認爲三條語句都被循環,而實際編譯器卻另有解釋,結果出乎意料。所以類似情況不要省略{}

語句結束分號的匹配

    C程序中分號是語句結束標誌,使用頻率最高。常在河邊走,哪能不溼鞋,一時手誤多寫或漏寫;號,有時會迷惑編譯器,導致Bug。比如:

    if (a > b);   //多加了一個;

      b = a;

    這段新代碼仍然通順,編譯器檢查不出語法錯誤,因爲它相當於:

    if (a > b) { }

    b = a;

但這樣的筆誤會導致結果和期望的大不相同,對外表現就是BUG;號多了不行,少了同樣有麻煩,如:

    if (a < 6)

       return     //少了;

    b = a;

    這裏return後漏了結束分號,程序能通過編譯,只是把b = a;當作return操作數,變成:

    if (a<6)

    return b = a;

    這樣當a >=6時,預想的b=a;會被跳過,因爲它和return一起成爲a<6的條件執行部分。

    還要注意結構體/枚舉/類等結尾的;符,if/for/while/switch等模塊體緊跟的{}後面都不需要;號,而struct/enum/class定義後必須有,如果遺漏就會導致語意錯誤,如:

struct pic{

      ……

}

test(){….}

    struct結尾與其後的函數test定義之間,遺漏了;號,變成聲明函數test,返回pic結構類型變量。如果;沒有遺漏,test缺省返回int型。

switch/case中的break

    switch/case結構中,case後的模塊中是否包含break,程序語法都正確,只是邏輯截然不同。如:

    switch(color)

    {

      case 1: printf ("red");

      case 2: printf ("yellow");

      case 3: printf ("blue");

    }

    假設color值是2,輸出結果是yellowblue,而不是有人預想的yellow,因爲一旦某case分支缺少break,流程執行完當前case後,自然轉向下一case,直到break出現才跳出。這似乎讓人費解,明明case 3並不成立,怎麼它的條件分支也會執行?問題在於人們潛意識裏總把case等同於if,但編譯器並不這麼認爲。不要把switch/case簡單和if/else畫等號,C不會提供兩個功能完全互換的功能。

    switch/case這種特殊流程是雙刃劍。一方面,某些情況下故意去掉break,能方便實現if/else等其他語句很難實現的特殊控制流程。另一方面,寫代碼時不小心遺漏break語句,編譯器不能識別並提醒,只會認爲程序員有意這麼做,包不包括break;都成立時,這種“程序員總正確”的推定就產生bug

形似的單雙目運算符

    C語言爲編譯器以及編程的方便,最大程度“複用”一些字符,爲使用概率高的操作分配較短的符號,以提高整體效率。&&&|||以及=“==”,都是這種情況,比如用=表示賦值而用==表示比較,因爲賦值操作出現概率高於比較,所以爲其分配更短的符號。

    儘管這種方法使C更加簡潔,但由於符號極度相似,稍不注意,就會因筆誤出現混淆。估計所有C程序員都犯過類似下面的錯誤:

    if(i=0)    //原意應爲if(i == 0)

    這種下意識的隨手錯誤很難避免,畢竟一個“=”號人們熟悉了幾十年,而“==”號相對陌生。===方便了編譯器,卻給程序員們設了一條絆馬索。甚至有些錯誤,除了作者本人調式,其他任何程序員也不可能檢查出來,更別說編譯器了。如:

if(a&b)  //原意if(a && b)

    &&&哪個都通,編譯器不報錯,其他人也不可能發現問題。但如果原來預想實現是a&&b,現在的實現就是bug。所以這幾對單雙目運算符使用時要多注意。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章