printf("I read %d and %f/n", i, f); return 0; }[/code:1:db26774567] 測試運行 Enter an integer and a float: 182 52.38 I read 182 and 52.380001 另一個測試運行 Enter an integer and a float: 6713247896 4.4 I read -1876686696 and 4.400000 •++ 和 -- 當對語句中的變量使用遞增或遞減運算符時,該變量不應在語句中出現一次以上,因為求值的順序取決于編譯器。編寫代碼時不要對順序作假設,也不要編寫在某一機器上能夠如期運作但沒有明確定義的行為的代碼:
[code:1:db26774567] int i = 0, a[5];
a[i] = i++; /* assign to a[0]? or a[1]? */[/code:1:db26774567] •不要被表面現象迷惑 請看以下示例:
[code:1:db26774567] while (c == '/t' c = ' ' c == '/n') c = getc(f);[/code:1:db26774567] 乍一看,while 子句中的語句似乎是有效的 C 代碼。但是,使用賦值運算符而不是比較運算符卻產生了語義上不正確的代碼。= 的優先級在所有運算符中是最低的,因此將以下列方式解釋該語句(為清楚起見添加了括號):
[code:1:db26774567] while ((c == '/t' c) = (' ' c == '/n')) c = getc(f);[/code:1:db26774567] 賦值運算符左邊的子句是:
[code:1:db26774567] while (c == '/t' c == ' ' c == '/n') c = getc(f);[/code:1:db26774567] 而是編寫:
[code:1:db26774567] while ('/t' == c ' ' == c '/n' == c) c = getc(f);[/code:1:db26774567] 用以下方法卻會得到編譯器診斷:
[code:1:db26774567] while ('/t' = c ' ' == c '/n' == c) c = getc(f);[/code:1:db26774567] 這種風格讓編譯器發現問題;上面的語句是無效的,因為它試圖對“/t”賦值。 •意想不到的麻煩。 各種 C 實現通常在某些方面各有不同。堅持使用語言中可能對所有實現都是公共的部分會有幫助。通過這樣做,您更輕易將程序移植到新的機器或編譯器,并且不大會碰到編譯器非凡性所帶來的問題。例如,考慮字符串:
[code:1:db26774567] if (a == 1) if (b == 2) printf("***/n"); else printf("###/n");[/code:1:db26774567]
規則是 else 附加至最近的 if。當有疑慮時,或有不明確的可能時,添加花括號以說明代碼的塊結構。 •數組界限 檢查所有數組的數組界限,包括字符串,因為在您現在輸入“fubar”的地方,有人可能會輸入“floccinaucinihilipilification”。健壯的軟件產品不應使用 gets()。 C 下標以零作為開始的這一事實使所有的計數問題變得更簡單。然而,把握如何處理它們需要花些努力。 •空語句 for 或 while 循環的空語句體應當單獨位于一行并加上注釋,這樣就表明這個空語句體是有意放置的,而不是遺漏了代碼。
[code:1:db26774567] if (func() == TRUE) {...[/code:1:db26774567] 寫成
[code:1:db26774567] if (func() != FALSE)[/code:1:db26774567] •嵌入語句 使用嵌入賦值語句要看時間和地點。在有些構造中,假如不使用更多且不易閱讀的代碼就沒有更好的方法來實現結果:
[code:1:db26774567] while ((c = getchar()) != EOF) { process the character }[/code:1:db26774567] 使用嵌入賦值語句來提高運行時性能是可能的。但是,您應當在提高速度和降低可維護性之間加以權衡,在人為指定的位置使用嵌入賦值語句會導致可維護性降低。例如:
[code:1:db26774567] x = y + z; d = x + r;[/code:1:db26774567] 不應被替換為:
[code:1:db26774567] d = (x = y + z) + r;[/code:1:db26774567] 即使后者可能節省一個周期也不行。最終,這兩者之間在運行時間上的差異將隨著優化器的增強而減少,易維護性的差異卻將增加。 •goto 語句 應保守地使用 goto。從數層 switch、for 和 while 嵌套中跳出來時,使用該語句很有效,不過,假如有這樣的需要,則表明應將內部構造分解成單獨的函數。
[code:1:db26774567] for (...) { while (...) { ... if (wrong) goto error;
} } ... error: print a message[/code:1:db26774567] 當必須使用 goto 時,隨附的標號應單獨位于一行,并且同后續代碼的左邊相距一個制表符或位于一行的開頭。對 goto 語句和目標都應加上注釋,說明其作用和目的。 •switch 中的“落空”(fall-through) 當一塊代碼有數個標號時,將這些標號放在單獨的行。這種風格與垂直空格的使用一致,并且使重新安排 case 選項(假如那是必需的話)成了一項簡單的任務。應對 C switch 語句的“落空”特征加以注釋,以便于以后的維護。假如這一特性曾給您帶來“麻煩”,那么您就能夠理解這樣做的重要性!
[code:1:db26774567] switch (eXPr) { case ABC: case DEF: statement; break; case UVW: statement; /*FALLTHROUGH*/ case XYZ: statement; break; }[/code:1:db26774567] 盡管從技術上說,最后一個 break 不是必需的,但是,假如以后要在最后一個 case 之后添加了另一個 case,那么一致地使用 break 可以防止“落空”錯誤。假如使用 default case 語句的話, 它應當永遠是最后一個,并且(假如它是最后的語句)不需要最后的 break 語句。 •常量 符號常量使代碼更易于閱讀。應盡量避免使用數字常量;使用 C 預處理器的 #define 函數給常量賦予一個有意義的名稱。在一個位置(最好在頭文件中)定義值還會使得治理大型程序變得更輕易,因為只需更改定義就可以統一地更改常量值??梢钥紤]使用枚舉數據類型作為對聲明只取一組離散值的變量的改進方法。使用枚舉還可以讓編譯器對您枚舉類型的任何誤用發出警告。任何直接編碼的數字常量必須至少有一個說明值的出處的注釋。 常量的定義與它的使用應該一致;例如,將 540.0 用于浮點數,而不要通過隱式浮點類型強制轉換使用 540。也就是說,在有些情況下,常量 0 和 1 可以以本身的形式直接出現,而不要以定義的形式出現。例如,假如某個 for 循環遍歷一個數組,那么:
[code:1:db26774567] for (i = 0; i < arraysub; i++)[/code:1:db26774567] 非常合理,而代碼:
[code:1:db26774567] gate_t *front_gate = opens(gate[i], 7); if (front_gate == 0) error("can't open %s/n", gate[i]);[/code:1:db26774567] 就不合理。在第二個示例中,front_gate 是指針;當值是指針時,它應與 NULL 比較而不與 0 比較。即使象 1 或 0 這樣的簡單值,通常最好也使用象 TRUE 和 FALSE 這樣的定義來表示(有時 YES 和 NO 讀起來更清楚)。 不要在需要離散值的地方使用浮點變量。這是由于浮點數不精確的表示決定的(請參閱以上 scanf 中的第二個測試)。使用 <= 或 >= 測試浮點數;精確比較(== 或 !=)也許不能檢測出“可接受的”等同性。 應將簡單的字符常量定義為字符文字而不是數字。不提倡使用非文本字符,因為它們是不可移植的。假如必須使用非文本字符,尤其是在字符串中使用它們,則應使用三位八進制數(不是一個字符)的轉義字符(例如“/007”)來編寫它們。即便如此,這樣的用法應視為與機器相關,并且應按這一情況來處理。 •條件編譯 條件編譯可用于機器相關性、調試以及在編譯時設置某些選項??梢杂脽o法預料的方式輕易地組合各種控制。假如將 #ifdef 用于機器相關性,應確保當沒有指定機器時會出錯,而不是使用缺省的機器。#error 偽指令可以較方便地用于這一用途。假如使用 #ifdef 進行優化,缺省值應是未優化的代碼而不是不可編譯或不正確的程序。要確保對未優化的代碼進行了測試。 [size=18:db26774567][b:db26774567]其它[/b:db26774567][/size:db26774567] •象 Make 這樣用于編譯和鏈接的實用程序極大簡化了將應用程序從一個環境移到另一個環境的任務。在開發期間,make 僅對那些自上次使用 make 以來發生了更改的模塊進行重新編譯。 經常使用 lint。lint 是 C 程序檢查器,它檢查 C 源文件以檢測并報告函數定義和調用之間類型的不匹配和不一致,以及可能存在的程序錯誤等。 此外,研究一下編譯器文檔,了解那些使編譯器變得“吹毛求疵”的開關。編譯器的工作是力求精確,因此通過使用適當的命令行選項讓它報告可能存在的錯誤。
•使應用程序中全局符號的數量最少。這樣做的好處之一是與系統定義的函數沖突的可能性降低。 •許多程序在遺漏輸入時會失敗。對所有的程序都應進行空輸入測試。這也可能幫助您理解程序的工作原理。 •不要對您的用戶或您所用的語言實現有任何過多的假設。那些“不可能發生”的事情有時的確會發生。健壯的程序可以防范這樣的情形。假如需要找到某個邊界條件,您的用戶將以某種方式找到它! 永遠不要對給定類型的大小作任何假設,尤其是指針。 當在表達式中使用 char 類型時,大多數實現將它們當作無符號類型,但有些實現把它們作為有符號的類型。當在算術表達式使用它們時,建議始終對它們進行類型強制轉換。 不要依靠對自動變量和 malloc 返回的內存進行的初始化。 •使您程序的目的和結構清楚。 •要記住,可能會在以后要求您或別的人修改您的代碼或在別的機器上運行它。細心編寫您的代碼,以便能夠將它移植到其它機器。 [size=18:db26774567][b:db26774567]結束語[/b:db26774567][/size:db26774567] 應用程序的維護要花去程序員的大量時間,這是眾所周知的事。部分原因是由于在開發應用程序時,使用了不可移植和非標準的特性,以及不令人滿足的編程風格。在本文中,我們介紹了一些指南,多年來它們一直給予我們很大幫助。我們相信,只要遵守這些指南,將可以使應用程序維護在團隊環境中變得更輕易。 [size=18:db26774567][b:db26774567]參考資料[/b:db26774567][/size:db26774567] •Obfuscated C and Other Mysteries,由 Don Libes 編寫,John Wiley and Sons, Inc. ISBN 0-471-57805-3 •The C Programming Language,Second Edition,由 Brian W. Kernighan 和 Dennis M. Ritchie 撰寫,Prentice-Hall,ISBN 0-13-110370-9 •Safer C,由 Les Hatton 編寫,McGraw-Hill,ISBN 0-07-707640-0 •C Traps and Pitfalls 由 Andrew Koenig 編寫,AT&T Bell Laboratories,ISBN 0-201-17928-9