盡管 C 語(yǔ)言問(wèn)世已近 30 年,但它的魅力仍未減退。C 語(yǔ)言繼續(xù)吸引著眾多的人們,他們?yōu)榱司帉?xiě)新的應(yīng)用程序,或者移植或維護(hù)現(xiàn)有的應(yīng)用程序而必須學(xué)習(xí)新技能。

簡(jiǎn)介

本文是為了滿足開(kāi)發(fā)人員的需要而寫(xiě)的。我們總結(jié)了一套指南,無(wú)論作為開(kāi)發(fā)人員還是顧問(wèn),這些指南多年來(lái)一直都很好地指導(dǎo)著我們,我們把它們作為建議提供給您,希望對(duì)您的工作有所幫助。您也許不贊同其中的某些指南,但我們希望您會(huì)喜歡其中的一些并在您的編程或移植項(xiàng)目中使用它們。

風(fēng)格與指南

l          使用一種使代碼具有可讀性和一致性的源代碼風(fēng)格。如果沒(méi)有團(tuán)隊(duì)代碼風(fēng)格或自己的風(fēng)格,您可以使用與大多數(shù) C 程序員采用的 Kernighan Ritchie 風(fēng)格相似的風(fēng)格。然而,舉一個(gè)極端的例子,有可能最終會(huì)寫(xiě)出與下面相似的代碼:

    int i;main(){for(;i["]<i;++i){--i;}"];read('-'-'-',i+++"hell\

    o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/i);

1984 年模糊 C 代碼大賽“差勁獎(jiǎng)”。應(yīng)代碼作者要求匿名。

l          通常將主例程定義為 main()。對(duì)應(yīng)的 ANSI 編寫(xiě)方式是 int main(void)(如果不考慮命令行參數(shù)的話)或 int main( int argc, char **argv )ANSI 以前的編譯器會(huì)省略 void聲明,或列出變量名以及緊隨其后的聲明。

l          空格

充分利用水平和垂直空格。縮進(jìn)和空格間距應(yīng)反映出代碼的塊結(jié)構(gòu)。

應(yīng)將條件運(yùn)算符的長(zhǎng)字符串分割成單獨(dú)的幾行。例如:

 

    if (foo->next==NULL && number < limit      && limit <=SIZE

        && node_active(this_input)) {...

 

最好改成:

 

    if (foo->next == NULL

          && number < limit && limit <= SIZE

          && node_active(this_input))

        {

         ...

 

同樣,應(yīng)將描述得很詳細(xì)的 for 循環(huán)分割成不同的行:

 

    for (curr = *varp, trail = varp;

        curr != NULL;

       trail = &(curr->next), curr = curr->next )

     {

           ...

 

對(duì)于其它復(fù)雜表達(dá)式(如使用三元運(yùn)算符 ?:的表達(dá)式),最好也將其分割成數(shù)行。

 

    z = (x == y)

        ? n + f(x)

         : f(y) - n;

                     

 

l          注釋

注釋?xiě)?yīng)描述正在發(fā)生什么事、如何完成它、參數(shù)表示什么、使用了哪些全局變量以及任何限制或錯(cuò)誤。但要避免不必要的注釋。如果代碼比較清晰,并且使用了良好的變量名,那么它應(yīng)該能夠較好地說(shuō)明自身。因?yàn)榫幾g器不檢查注釋,所以不保證它們是正確的。與代碼不一致的注釋會(huì)起到相反的作用。過(guò)多的注釋會(huì)使代碼混亂。

下面是一種多余的注釋風(fēng)格:

 

    i=i+1;        /* Add one to i */

 

很明顯變量 i遞增了 1。還有更糟的注釋方法:

 

 

         /************************************

         *                                   *

         *          Add one to i           *

         *                                   *

           ************************************/

 

                    i=i+1;

 

l          命名約定

具有前導(dǎo)和尾隨下劃線的名稱是為系統(tǒng)用途而保留的,不應(yīng)當(dāng)用于任何用戶創(chuàng)建的名稱。約定規(guī)定:

1.          #define 常量應(yīng)全部大寫(xiě)。

2.          enum 常量應(yīng)以大寫(xiě)字母開(kāi)頭或全部大寫(xiě)。

3.          函數(shù)、類型定義(typedef)和變量名以及結(jié)構(gòu)(struct)、聯(lián)合(union)和枚舉(enum)標(biāo)記名稱應(yīng)小寫(xiě)。

為清晰起見(jiàn),避免使用僅在大小寫(xiě)上有區(qū)別的名稱,如 foo Foo。同樣,避免使用 foobar foo_bar 這樣的名稱。避免使用看上去相似的名稱。在許多終端和打印機(jī)上,“l”、“1”和“I”看上去非常相似。使用名為“l”的變量非常不明智,因?yàn)樗瓷先シ浅O蟪A?#8220;1”。

 

l          變量名

選擇變量名時(shí),長(zhǎng)度不重要,但清晰的表達(dá)很重要。長(zhǎng)名稱可用于全局變量,因?yàn)樗怀S茫鴮⒃诿啃醒h(huán)上要使用的數(shù)組下標(biāo)命名為 i 就完全夠了。如果使用“index”或“elementnumber”的話,不僅輸入得更多,而且會(huì)使計(jì)算的細(xì)節(jié)不明確。如果使用長(zhǎng)變量名,有時(shí)候會(huì)使代碼更難理解。比較:

 

 

     for(i=0 to 100)

          array[i]=0

 

 

 

     for(elementnumber=0 to 100)

          array[elementnumber]=0;

 

l          函數(shù)名

函數(shù)名應(yīng)反映函數(shù)執(zhí)行什么操作以及返回什么內(nèi)容。函數(shù)在表達(dá)式中使用,通常用于 if子句,因此它們的意圖應(yīng)一目了然。例如:

 

 

     if (checksize(x))

 

沒(méi)有幫助作用,因?yàn)樗鼪](méi)有告訴我們 checksize 是在出錯(cuò)時(shí)返回 true 還是在不出錯(cuò)時(shí)返回 true;而

 

     if (validsize(x))

 

則使函數(shù)的意圖很明確。

l          聲明

所有的外部數(shù)據(jù)聲明前都應(yīng)加上 extern關(guān)鍵字。

“指針”限定符“*”應(yīng)緊鄰變量名而不是類型。例如,應(yīng)使用

 

 

    char        *s, *t, *u;

 

而不是

 

    char*   s, t, u;

 

后一條語(yǔ)句沒(méi)有錯(cuò),但可能不是我們期望的,因?yàn)闆](méi)有將“t”和“u”聲明為指針。

l          頭文件

頭文件應(yīng)按功能組織在一起,即,對(duì)單獨(dú)子系統(tǒng)的聲明應(yīng)在單獨(dú)的頭文件中。此外,當(dāng)代碼從一個(gè)平臺(tái)移植到另一個(gè)平臺(tái)時(shí)有可能發(fā)生更改的聲明應(yīng)位于單獨(dú)的頭文件中。

 

避免使用與庫(kù)頭文件名相同的專用頭文件名。語(yǔ)句 #include "math.h" 如果在當(dāng)前目錄中找不到所期望文件的話,會(huì)包括標(biāo)準(zhǔn)庫(kù) math 頭文件。如果這是您期望的結(jié)果,可以注釋掉這行 include 語(yǔ)句。

 

最后說(shuō)明一點(diǎn),對(duì)頭文件使用絕對(duì)路徑名不是一個(gè)好主意。C 編譯器的“include-path”選項(xiàng)(在許多系統(tǒng)上為 -I 大寫(xiě)的 i)是處理眾多專用頭文件庫(kù)的首選方法;它允許在不改變?cè)次募那闆r下重新組織目錄結(jié)構(gòu)。

l          scanf

在重要的應(yīng)用程序中永遠(yuǎn)不要使用 scanf。它的錯(cuò)誤檢測(cè)不夠完善。請(qǐng)看下面的示例:

 

 

    #include <stdio.h>

 

    int main(void)

    {

        int i;

        float f;

 

        printf("Enter an integer and a float: ");

        scanf("%d %f", &i, &f);

 

        printf("I read %d and %f\n", i, f);

        return 0;

    }

 

測(cè)試運(yùn)行

Enter an integer and a float: 182 52.38

I read 182 and 52.380001

另一個(gè)測(cè)試運(yùn)行

Enter an integer and a float: 6713247896 4.4

I read -1876686696 and 4.400000

l          ++ --

當(dāng)對(duì)語(yǔ)句中的變量使用遞增或遞減運(yùn)算符時(shí),該變量不應(yīng)在語(yǔ)句中出現(xiàn)一次以上,因?yàn)榍笾档捻樞蛉Q于編譯器。編寫(xiě)代碼時(shí)不要對(duì)順序作假設(shè),也不要編寫(xiě)在某一機(jī)器上能夠如期運(yùn)作但沒(méi)有明確定義的行為的代碼:

 

    int i = 0, a[5];

 

    a[i] = i++; /* assign to a[0]? or a[1]? */

 

l          不要被表面現(xiàn)象迷惑

請(qǐng)看以下示例:

 

 

        while (c == '\t' || c = ' ' || c == '\n')

            c = getc(f);

 

乍一看, while子句中的語(yǔ)句似乎是有效的 C 代碼。但是,使用賦值運(yùn)算符而不是比較運(yùn)算符卻產(chǎn)生了語(yǔ)義上不正確的代碼。= 的優(yōu)先級(jí)在所有運(yùn)算符中是最低的,因此將以下列方式解釋該語(yǔ)句(為清晰起見(jiàn)添加了括號(hào)):

 

        while ((c == '\t' || c) = (' ' || c == '\n'))

            c = getc(f);

 

賦值運(yùn)算符左邊的子句是:

 

        (c == '\t' || c)

 

它不會(huì)產(chǎn)生左值。如果 c 包含制表符,則結(jié)果是“true”,并且不會(huì)執(zhí)行進(jìn)一步的求值,而“true”不能位于賦值表達(dá)式的左邊。

l          意圖要明確。

當(dāng)您編寫(xiě)的代碼可以解釋成另一種意圖時(shí),使用括號(hào)或用其它方法以確保您的意圖清楚。如果您以后必須處理該程序的話,這有助于您理解您當(dāng)初的意圖。如果其他人要維護(hù)該代碼,這可以讓維護(hù)任務(wù)變得更簡(jiǎn)單。

用能預(yù)見(jiàn)可能出現(xiàn)錯(cuò)誤的方式編碼,有時(shí)是可行的。例如,可以將常量放在比較等式的左邊。即,不編寫(xiě):

 

    while (c == '\t' || c == ' ' || c == '\n')

        c = getc(f);

 

而是編寫(xiě):

 

    while ('\t' == c || ' ' == c || '\n' == c)

         c = getc(f);

 

用以下方法卻會(huì)得到編譯器診斷:

 

    while ('\t' = c || ' ' == c || '\n' == c)

         c = getc(f);

 

這種風(fēng)格讓編譯器發(fā)現(xiàn)問(wèn)題;上面的語(yǔ)句是無(wú)效的,因?yàn)樗噲D對(duì)“\t”賦值。

l          意想不到的麻煩。

各種 C 實(shí)現(xiàn)通常在某些方面各有不同。堅(jiān)持使用語(yǔ)言中可能對(duì)所有實(shí)現(xiàn)都是公共的部分會(huì)有幫助。通過(guò)這樣做,您更容易將程序移植到新的機(jī)器或編譯器,并且不大會(huì)遇到編譯器特殊性所帶來(lái)的問(wèn)題。例如,考慮字符串:

 

    /*/*/2*/**/1

 

這里利用了“最大適合(maximal munch)”規(guī)則。如果可以嵌套注釋,則可將該字符串解釋為:

    /* /* /2 */ * */ 1

 

兩個(gè) /* 符號(hào)與兩個(gè) */ 符號(hào)匹配,因此該字符串的值為 1。如果注釋不嵌套,那么在有些系統(tǒng)上,注釋中的 /* 就被忽略。在另一些系統(tǒng)上會(huì)針對(duì) /* 發(fā)出警告。無(wú)論哪種情況,該表達(dá)式可解釋為:

 

    /* / */ 2 * /* */ 1

 

2 * 1 求值得 2

l          清空輸出緩沖區(qū)

當(dāng)應(yīng)用程序異常終止時(shí),其輸出的尾部常常會(huì)丟失。應(yīng)用程序可能沒(méi)有機(jī)會(huì)完全清空它的輸出緩沖區(qū)。輸出的某一部分可能仍在內(nèi)存中,并且永遠(yuǎn)不會(huì)被寫(xiě)出。在有些系統(tǒng)上,這一輸出可能有幾頁(yè)長(zhǎng)。

以這種方式丟失輸出會(huì)使人誤解,因?yàn)樗o人的印象是程序在它實(shí)際失敗很久之前就失敗了。解決這一問(wèn)題的方法是強(qiáng)制將輸出從緩沖區(qū)清除,特別是在調(diào)試期間。確切的方法隨系統(tǒng)的不同而有所不同,不過(guò)也有常用的方法,如下所示:

 

    setbuf(stdout, (char *) 0);

 

必須在將任何內(nèi)容寫(xiě)到標(biāo)準(zhǔn)輸出之前執(zhí)行該語(yǔ)句。理想情況下,這將是主程序中的第一條語(yǔ)句。

l          getchar() 宏還是函數(shù)

以下程序?qū)⑵漭斎霃?fù)制到其輸出:

 

 

    #include <stdio.h>

 

    int main(void)

    {

        register int a;

 

        while ((a = getchar()) != EOF)

            putchar(a);

    }

 

從該程序除去 #include 語(yǔ)句將使該程序無(wú)法編譯,因?yàn)?/span> EOF 將是未定義的。

我們可以用以下方法重新編寫(xiě)該程序:

 

 

    #define EOF -1

 

    int main(void)

    {

        register int a;

 

        while ((a = getchar()) != EOF)

            putchar(a);

    }

 

這在許多系統(tǒng)上都可行,但在有些系統(tǒng)上運(yùn)行要慢很多。

因?yàn)楹瘮?shù)調(diào)用通常要花較長(zhǎng)時(shí)間,所以常常把 getchar實(shí)現(xiàn)為宏。這個(gè)宏定義在 stdio.h中,所以當(dāng)除去 #include <stdio.h>時(shí),編譯器就不知道 getchar 是什么。在有些系統(tǒng)上,假設(shè) getchar 是返回一個(gè) int的函數(shù)。

實(shí)際上,許多 C 實(shí)現(xiàn)在其庫(kù)中都有 getchar函數(shù),部分原因是為了防止這樣的失誤。于是,在 #include <stdio.h>遺漏的情況下,編譯器使用 getchar的函數(shù)版本。函數(shù)調(diào)用的開(kāi)銷使程序變慢。 putchar有同樣的問(wèn)題。

l          空指針

空指針不指向任何對(duì)象。因此,為了賦值和比較以外的目的而使用空指針都是非法的。

不要重新定義 NULL 符號(hào)。NULL 符號(hào)應(yīng)始終是常量值零。任何給定類型的空指針總是等于常量零,而與值為零的變量或與某一非零常量的比較,其行為由實(shí)現(xiàn)定義。

反引用 null 指針可能會(huì)導(dǎo)致奇怪的事情發(fā)生。

l          a+++++b 表示什么?

解析它的唯一有意義的方法是:

 

        a ++ + ++ b

 

然而,“最大適合”規(guī)則要求將它分解為:

 

        a ++ ++ + b

 

這在語(yǔ)法上是無(wú)效的:它等于:

 

        ((a++)++) + b

 

a++ 的結(jié)果不是 左值,因此作為 ++ 的操作數(shù)是不可接受的。于是,解析詞法不明確性的規(guī)則使得以語(yǔ)法上有意義的方式解析該示例變得不可能。當(dāng)然,謹(jǐn)慎的辦法實(shí)際上是在不能完全確定它們的意義的情況下,避免這樣的構(gòu)造。當(dāng)然,添加空格有助于編譯器理解語(yǔ)句的意圖,但(從代碼維護(hù)的角度看)將這一構(gòu)造分割成多行更可取:

 

    ++b;

    (a++) + b;

 

l          小心處理函數(shù)

函數(shù)是 C 中最常用的結(jié)構(gòu)概念。它們應(yīng)用于實(shí)現(xiàn)“自頂向下的”問(wèn)題解決方法 即,將問(wèn)題分解成越來(lái)越小的子問(wèn)題,直到每個(gè)子問(wèn)題都能夠用代碼表示。這對(duì)程序的模塊化和文檔記錄有幫助。此外,由許多小函數(shù)組成的程序更易于調(diào)試。

如果有一些函數(shù)參數(shù)還不是期望的類型,則將它們強(qiáng)制轉(zhuǎn)換為期望的類型,即使您確信沒(méi)有必要也應(yīng)該這樣做,因?yàn)椋ㄈ绻晦D(zhuǎn)換的話)它們可能在您最意料不到的時(shí)候給您帶來(lái)麻煩。換句話說(shuō),編譯器通常將函數(shù)參數(shù)的類型提升和轉(zhuǎn)換成期望的數(shù)據(jù)類型以符合函數(shù)參數(shù)的聲明。但是,在代碼中以手工方式這樣做可以清楚地說(shuō)明程序員的意圖,并且在將代碼移植到其它平臺(tái)時(shí)能確保有正確的結(jié)果。

如果頭文件未能聲明庫(kù)函數(shù)的返回類型,那就自己聲明它們。用 #ifdef/#endif 語(yǔ)句將您的聲明括起來(lái),以備代碼被移植到另一個(gè)平臺(tái)。

函數(shù)原型應(yīng)當(dāng)用來(lái)使代碼更健壯,使它運(yùn)行得更快。

l          懸空 else

除非知道自己在做什么,否則應(yīng)避免“懸空 else”問(wèn)題:

 

 

        if (a == 1)

            if (b == 2)

                printf("***\n");

            else

                printf("###\n");

 

 

規(guī)則是 else 附加至最近的 if。當(dāng)有疑慮時(shí),或有不明確的可能時(shí),添加花括號(hào)以說(shuō)明代碼的塊結(jié)構(gòu)。

l          數(shù)組界限

檢查所有數(shù)組的數(shù)組界限,包括字符串,因?yàn)樵谀F(xiàn)在輸入“fubar”的地方,有人可能會(huì)輸入“floccinaucinihilipilification”。健壯的軟件產(chǎn)品不應(yīng)使用 gets()

C 下標(biāo)以零作為開(kāi)始的這一事實(shí)使所有的計(jì)數(shù)問(wèn)題變得更簡(jiǎn)單。然而,掌握如何處理它們需要花些努力。

l          空語(yǔ)句

for while 循環(huán)的空語(yǔ)句體應(yīng)當(dāng)單獨(dú)位于一行并加上注釋,這樣就表明這個(gè)空語(yǔ)句體是有意放置的,而不是遺漏了代碼。

 

    while (*dest++ = *src++)

    ;   /* VOID */

 

l          測(cè)試真(true)還是假(false

不要以缺省方式測(cè)試非零值,即:

 

    if (f() != FAIL)

 

優(yōu)于

 

    if (f())

 

盡管 FAIL 的值可能是 0(在 C 中視為假(false))。(當(dāng)然,應(yīng)當(dāng)在這一風(fēng)格與“函數(shù)名”一節(jié)中演示的構(gòu)造之間作出權(quán)衡。)當(dāng)以后有人認(rèn)為失敗的返回值應(yīng)該是 -1 而不是 0 時(shí),顯式的測(cè)試對(duì)您會(huì)有幫助。

常見(jiàn)的問(wèn)題是使用 strcmp 函數(shù)測(cè)試字符串是否相等,決不應(yīng)該以缺省方式處理它的結(jié)果。更可取的方法是定義宏 STREQ

 

#define STREQ(str1, str2) (strcmp((str1), (str2)) == 0)

 

用這種方法,語(yǔ)句

 

    If ( STREQ( inputstring, somestring ) ) ...

 

就具有隱含的行為,該行為不大會(huì)在您不知情的情況下改變(人們往往不會(huì)重新編寫(xiě)或重新定義象 strcmp()這樣的標(biāo)準(zhǔn)庫(kù)函數(shù))。

不要用 1 檢查相等性的布爾值(TRUE YES 等);而要用 0 測(cè)試不等性(FALSE NO 等)。絕大多數(shù)函數(shù)被確保在條件為假(false)時(shí)返回 0,但僅在條件為真(true)時(shí)才返回非零。因此,最好將

 

    if (func() == TRUE) {...

 

寫(xiě)成

 

    if (func() != FALSE)

 

l          嵌入語(yǔ)句

使用嵌入賦值語(yǔ)句要看時(shí)間和地點(diǎn)。在有些構(gòu)造中,如果不使用更多且不易閱讀的代碼就沒(méi)有更好的方法來(lái)實(shí)現(xiàn)結(jié)果:

 

    while ((c = getchar()) != EOF) {

    process the character

    }

 

使用嵌入賦值語(yǔ)句來(lái)提高運(yùn)行時(shí)性能是可能的。但是,您應(yīng)當(dāng)在提高速度和降低可維護(hù)性之間加以權(quán)衡,在人為指定的位置使用嵌入賦值語(yǔ)句會(huì)導(dǎo)致可維護(hù)性降低。例如:

 

    x = y + z;

    d = x + r;

 

不應(yīng)被替換為:

 

    d = (x = y + z) + r;

 

即使后者可能節(jié)省一個(gè)周期也不行。最終,這兩者之間在運(yùn)行時(shí)間上的差異將隨著優(yōu)化器的增強(qiáng)而減少,易維護(hù)性的差異卻將增加。

l          goto 語(yǔ)句

應(yīng)保守地使用 goto。從數(shù)層 switch for while嵌套中跳出來(lái)時(shí),使用該語(yǔ)句很有效,不過(guò),如果有這樣的需要,則表明應(yīng)將內(nèi)部構(gòu)造分解成單獨(dú)的函數(shù)。

 

        for (...) {

             while (...) {

           ...

                  if (wrong)

                       goto error;

       

                    }

         }

         ...

            error:

          print a message

 

當(dāng)必須使用 goto時(shí),隨附的標(biāo)號(hào)應(yīng)單獨(dú)位于一行,并且同后續(xù)代碼的左邊相距一個(gè)制表符或位于一行的開(kāi)頭。對(duì) goto語(yǔ)句和目標(biāo)都應(yīng)加上注釋,說(shuō)明其作用和目的。

l          switch 中的“落空”(fall-through

當(dāng)一塊代碼有數(shù)個(gè)標(biāo)號(hào)時(shí),將這些標(biāo)號(hào)放在單獨(dú)的行。這種風(fēng)格與垂直空格的使用一致,并且使重新安排 case 選項(xiàng)(如果那是必需的話)成了一項(xiàng)簡(jiǎn)單的任務(wù)。應(yīng)對(duì) C switch 語(yǔ)句的“落空”特征加以注釋,以便于以后的維護(hù)。如果這一特性曾給您帶來(lái)“麻煩”,那么您就能夠理解這樣做的重要性!

 

    switch (expr) {

    case ABC:  

    case DEF:

         statement;

         break;

    case UVW:

        statement; /*FALLTHROUGH*/

    case XYZ:

         statement;

     break; 

    }

 

盡管從技術(shù)上說(shuō),最后一個(gè) break 不是必需的,但是,如果以后要在最后一個(gè) case 之后添加了另一個(gè) case,那么一致地使用 break 可以防止“落空”錯(cuò)誤。如果使用 default case 語(yǔ)句的話, 它應(yīng)當(dāng)永遠(yuǎn)是最后一個(gè),并且(如果它是最后的語(yǔ)句)不需要最后的 break 語(yǔ)句。

l          常量

符號(hào)常量使代碼更易于閱讀。應(yīng)盡量避免使用數(shù)字常量;使用 C 預(yù)處理器的 #define 函數(shù)給常量賦予一個(gè)有意義的名稱。在一個(gè)位置(最好在頭文件中)定義值還會(huì)使得管理大型程序變得更容易,因?yàn)橹恍韪亩x就可以統(tǒng)一地更改常量值。可以考慮使用枚舉數(shù)據(jù)類型作為對(duì)聲明只取一組離散值的變量的改進(jìn)方法。使用枚舉還可以讓編譯器對(duì)您枚舉類型的任何誤用發(fā)出警告。任何直接編碼的數(shù)字常量必須至少有一個(gè)說(shuō)明值的出處的注釋。

常量的定義與它的使用應(yīng)該一致;例如,將 540.0 用于浮點(diǎn)數(shù),而不要通過(guò)隱式浮點(diǎn)類型強(qiáng)制轉(zhuǎn)換使用 540。也就是說(shuō),在有些情況下,常量 0 1 可以以本身的形式直接出現(xiàn),而不要以定義的形式出現(xiàn)。例如,如果某個(gè) for循環(huán)遍歷一個(gè)數(shù)組,那么:

 

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

 

非常合理,而代碼:

 

    gate_t *front_gate = opens(gate[i], 7);

    if (front_gate == 0)

       error("can't open %s\n", gate[i]);

 

就不合理。在第二個(gè)示例中,front_gate 是指針;當(dāng)值是指針時(shí),它應(yīng)與 NULL 比較而不與 0 比較。即使象 1 0 這樣的簡(jiǎn)單值,通常最好也使用象 TRUE FALSE 這樣的定義來(lái)表示(有時(shí) YES NO 讀起來(lái)更清楚)。

不要在需要離散值的地方使用浮點(diǎn)變量。這是由于浮點(diǎn)數(shù)不精確的表示決定的(請(qǐng)參閱以上 scanf中的第二個(gè)測(cè)試)。使用 <= >= 測(cè)試浮點(diǎn)數(shù);精確比較(== !=)也許不能檢測(cè)出“可接受的”等同性。

應(yīng)將簡(jiǎn)單的字符常量定義為字符文字而不是數(shù)字。不提倡使用非文本字符,因?yàn)樗鼈兪遣豢梢浦驳摹H绻仨毷褂梅俏谋咀址绕涫窃谧址惺褂盟鼈儯瑒t應(yīng)使用三位八進(jìn)制數(shù)(不是一個(gè)字符)的轉(zhuǎn)義字符(例如“\007”)來(lái)編寫(xiě)它們。即便如此,這樣的用法應(yīng)視為與機(jī)器相關(guān),并且應(yīng)按這一情況來(lái)處理。

l          條件編譯

條件編譯可用于機(jī)器相關(guān)性、調(diào)試以及在編譯時(shí)設(shè)置某些選項(xiàng)。可以用無(wú)法預(yù)料的方式輕易地組合各種控制。如果將 #ifdef 用于機(jī)器相關(guān)性,應(yīng)確保當(dāng)沒(méi)有指定機(jī)器時(shí)會(huì)出錯(cuò),而不是使用缺省的機(jī)器。#error 偽指令可以較方便地用于這一用途。如果使用 #ifdef 進(jìn)行優(yōu)化,缺省值應(yīng)是未優(yōu)化的代碼而不是不可編譯或不正確的程序。要確保對(duì)未優(yōu)化的代碼進(jìn)行了測(cè)試。

 

其它

l          Make這樣用于編譯和鏈接的實(shí)用程序極大簡(jiǎn)化了將應(yīng)用程序從一個(gè)環(huán)境移到另一個(gè)環(huán)境的任務(wù)。在開(kāi)發(fā)期間, make僅對(duì)那些自上次使用 make 以來(lái)發(fā)生了更改的模塊進(jìn)行重新編譯。

l          經(jīng)常使用 lint lint C 程序檢查器,它檢查 C 源文件以檢測(cè)并報(bào)告函數(shù)定義和調(diào)用之間類型的不匹配和不一致,以及可能存在的程序錯(cuò)誤等。

l          此外,研究一下編譯器文檔,了解那些使編譯器變得“吹毛求疵”的開(kāi)關(guān)。編譯器的工作是力求精確,因此通過(guò)使用適當(dāng)?shù)拿钚羞x項(xiàng)讓它報(bào)告可能存在的錯(cuò)誤。

l          使應(yīng)用程序中全局符號(hào)的數(shù)量最少。這樣做的好處之一是與系統(tǒng)定義的函數(shù)沖突的可能性降低。

l          許多程序在遺漏輸入時(shí)會(huì)失敗。對(duì)所有的程序都應(yīng)進(jìn)行空輸入測(cè)試。這也可能幫助您理解程序的工作原理。

l          不要對(duì)您的用戶或您所用的語(yǔ)言實(shí)現(xiàn)有任何過(guò)多的假設(shè)。那些“不可能發(fā)生”的事情有時(shí)的確會(huì)發(fā)生。健壯的程序可以防范這樣的情形。如果需要找到某個(gè)邊界條件,您的用戶將以某種方式找到它!

l          永遠(yuǎn)不要對(duì)給定類型的大小作任何假設(shè),尤其是指針。

l          當(dāng)在表達(dá)式中使用 char類型時(shí),大多數(shù)實(shí)現(xiàn)將它們當(dāng)作無(wú)符號(hào)類型,但有些實(shí)現(xiàn)把它們作為有符號(hào)的類型。當(dāng)在算術(shù)表達(dá)式使用它們時(shí),建議始終對(duì)它們進(jìn)行類型強(qiáng)制轉(zhuǎn)換。

l          不要依靠對(duì)自動(dòng)變量和 malloc返回的內(nèi)存進(jìn)行的初始化。

l          使您程序的目的和結(jié)構(gòu)清晰。

l          要記住,可能會(huì)在以后要求您或別的人修改您的代碼或在別的機(jī)器上運(yùn)行它。細(xì)心編寫(xiě)您的代碼,以便能夠?qū)⑺浦驳狡渌鼨C(jī)器。

 

結(jié)束語(yǔ)

應(yīng)用程序的維護(hù)要花去程序員的大量時(shí)間,這是眾所周知的事。部分原因是由于在開(kāi)發(fā)應(yīng)用程序時(shí),使用了不可移植和非標(biāo)準(zhǔn)的特性,以及不令人滿意的編程風(fēng)格。在本文中,我們介紹了一些指南,多年來(lái)它們一直給予我們很大幫助。我們相信,只要遵守這些指南,將可以使應(yīng)用程序維護(hù)在團(tuán)隊(duì)環(huán)境中變得更容易。

參考資料

l          Obfuscated C and Other Mysteries,由 Don Libes 編寫(xiě),John Wiley and Sons, Inc. ISBN 0-471-57805-3

l          The C Programming LanguageSecond Edition,由 Brian W. Kernighan Dennis M. Ritchie 撰寫(xiě),Prentice-HallISBN 0-13-110370-9

l          Safer C,由 Les Hatton 編寫(xiě),McGraw-HillISBN 0-07-707640-0

l          C Traps and Pitfalls Andrew Koenig 編寫(xiě),AT&T Bell LaboratoriesISBN 0-201-17928-9

作者簡(jiǎn)介

Shiv Dutta IBM Systems Group 的一名技術(shù)顧問(wèn),他幫助獨(dú)立軟件供應(yīng)商在 pSeries 服務(wù)器啟用他們的應(yīng)用程序。Shiv 有作為軟件開(kāi)發(fā)人員、系統(tǒng)管理員和講師的豐富經(jīng)驗(yàn)。他在 AIX 的系統(tǒng)管理、問(wèn)題確定、性能調(diào)優(yōu)和規(guī)模指導(dǎo)方面提供支持。Shiv AIX 誕生之時(shí)就從事這方面的工作。他從 Ohio University 獲得物理博士學(xué)位,可以通過(guò) sdutta@us.ibm.com與他聯(lián)系。

Gary R. Hook IBM 的高級(jí)技術(shù)顧問(wèn),為獨(dú)立軟件供應(yīng)商提供應(yīng)用程序開(kāi)發(fā)、移植和技術(shù)援助。Hook 先生的職業(yè)經(jīng)歷主要在基于 Unix 的應(yīng)用程序開(kāi)發(fā)方面。在 1990 年加入 IBM 時(shí),他在位于得克薩斯州 Southlake AIX Technical Support 中心工作,為客戶提供咨詢和技術(shù)支持服務(wù),重點(diǎn)在 AIX 應(yīng)用程序體系結(jié)構(gòu)方面。Hook 先生現(xiàn)在居住在奧斯汀,在 1995 2000 年期間,他是 AIX Kernel Development 團(tuán)隊(duì)的一員,專門(mén)研究 AIX 鏈接程序、裝入程序和通用應(yīng)用程序開(kāi)發(fā)工具。可以通過(guò) ghook@us.ibm.com與他聯(lián)