欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Recommended C Style and Coding Standards中文翻譯版第3/3頁

 更新時間:2014年04月28日 11:45:23   作者:  
本文翻譯自Recommended C Style and Coding Standards(C語言編碼風(fēng)格和標(biāo)準(zhǔn)),需要的朋友可以參考下


16. 可移植性

    "C語言結(jié)合了匯編的強(qiáng)大功能和可移植性" — 無名氏,暗指比爾.薩克。

可移植代碼的好處是有目共睹的。這一節(jié)將闡述一些編寫可移植代碼的指導(dǎo)原則。這里"可移植的"是指一個源碼文件能夠在不同機(jī)器上被編譯和執(zhí)行,其 前提僅僅是在不同平臺上可能包含不同的頭文件,使用不同的編譯器開關(guān)選項(xiàng)罷了。頭文件包含的#define和typedef可能因機(jī)器而異。一般 來說,一個新"機(jī)器"是指一種不同的硬件,一種不同的操作系統(tǒng),一個不同的編譯器,或者是這些的任意組合。參考1包含了很多關(guān)于風(fēng)格和可移植 性方面的有用信息。下面是一個隱患列表,當(dāng)你設(shè)計(jì)可移植代碼時應(yīng)該考慮避免這些隱患:

    * 編寫可移植的代碼。只有當(dāng)被證明是必要的情況下才考慮優(yōu)化的細(xì)節(jié)。優(yōu)化后的代碼往往是模糊不清、難以理解的。在一臺機(jī)器上經(jīng)過優(yōu)化后的代碼,在其他機(jī)器上 可能變得更加糟糕。將采用的性能優(yōu)化手段記錄下來并盡可能多地本地化。文檔應(yīng)該解釋這些手段的工作原理以及引入它們的原因(例如:"循環(huán)執(zhí)行了無 數(shù)次")

    * 要意識到很多東西天生就是不可移植的。比如處理類似程序狀態(tài)字這樣的特定硬件寄存器的代碼,以及被設(shè)計(jì)用于支持某特定硬件部件的代碼,諸如匯編器以及 I/O驅(qū)動。即使在這種情況下,許多例程和數(shù)據(jù)仍然可以被設(shè)計(jì)成機(jī)器無關(guān)的。

    * 組織源文件時將機(jī)器無關(guān)與機(jī)器相關(guān)的代碼分別放在不同文件中。之后如果這個程序需要被移植到一個新機(jī)器上時,我們就可以很容易判斷出來哪些需要被改變。為 一些文件的頭文件中機(jī)器依賴相關(guān)的代碼添加注釋。

    * 任何"實(shí)現(xiàn)相關(guān)"的行為都應(yīng)該作為機(jī)器(編譯器)依賴對待。假設(shè)編譯器或硬件以一種十分古怪的方式實(shí)現(xiàn)它。

    * 注意機(jī)器字長。對象的大小可能不直觀,指針大小也不總是與整型大小相同,也不總是彼此大小相同,或者可相互自由轉(zhuǎn)換。下面的表中列舉了C語言基本類型在不 同機(jī)器和編譯器下的大小(以bit為單位)。

復(fù)制代碼 代碼如下:

type    pdp11  VAX/11 68000  Cray-2 Unisys Harris 80386
        series       family          1100   H800
char     8      8      8       8      9     8      8
short    16    16     8/16   64(32)   18    24    8/16
int      16    32     16/32  64(32)   36    24    16/32
long     32    32      32    64       36    48    32
char*    16    32      32    64       72    24    16/32/48
int*     16    32      32    64(24)   72    24    16/32/48
int(*)() 16    32      32    64       576   24    16/32/48

有些機(jī)器針對某一類型可能有不止一個大小。其類型大小取決于編譯器和不同的編譯期標(biāo)志。下面表展示了大多數(shù)系統(tǒng)的"安全"類型大小。無符號與帶符 號數(shù)具有相同的大小(單位:bit)。

復(fù)制代碼 代碼如下:

Type    Minimum  No Smaller
          # Bits   Than
char       8 
short      16    char
int        16    short
long       32    int
float      24 
double     38    float
any *      14 
char *     15    any *
void *     15    any *

    * void類型可以保證有足夠位精度來表示一個指向任意數(shù)據(jù)對象的指針。void()()類型可以保證表示一個指向任意函數(shù)的指針。當(dāng)你需要通用指針時 可以使用這些類型(在一些舊的編譯器里,分別用char和char()()表示)。確保在使用這些指針類型之前將其轉(zhuǎn)換回正確的類型。

    * 即使說一個int和一個char類型大小相同,它們?nèi)钥赡芫哂胁煌母袷健@?,下面例子在一些sizeof(int)等于 sizeof(char)的機(jī)器上可能失敗。其原因在與free函數(shù)期望一個char,但卻傳入了一個int。

復(fù)制代碼 代碼如下:

    int *p = (int *) malloc (sizeof(int));
   free (p);

    * 注意,一個對象的大小不能保證這個對象的精度。Cray-2可能使用64位來存儲一個整型,但一個長整型轉(zhuǎn)換為一個整型并且再轉(zhuǎn)換回長整型后可能會被截?cái)?為32位。

    * 整型常量0可以強(qiáng)制轉(zhuǎn)型為任何指針類型。轉(zhuǎn)換后的指針稱為對應(yīng)那個類型的空指針,并且與那個類型的其他指針不同??罩羔槺容^總是與常量0相當(dāng)??罩羔槻粦?yīng) 該與一個值為0的變量比較??罩羔槻豢偸鞘褂萌?的位模式表示。兩個不同類型的空指針有些時候可能不同。某個類型的空指針被強(qiáng)制轉(zhuǎn)換為另外一個類 型的指針,其結(jié)果是該指針轉(zhuǎn)換為第二個類型的空指針。

    * 對于ANSI編譯器,當(dāng)兩個類型相同的指針訪問同一塊存儲區(qū)時,則它們比較是相等的。當(dāng)一個非0整型常量被轉(zhuǎn)換為指針類型時,它們可能與其他指針相等。對 于非ANSI編譯器,訪問同一塊存儲區(qū)的兩個指針比較可能并不相同。例如,下面兩個指針比較可能相等或不相等,并且他們可能或可能沒有訪問同一塊 存儲區(qū)域。

復(fù)制代碼 代碼如下:

    ((int *) 2 )
    ((int *) 3 )

如果你需要'magic'指針而不是NULL,要么分配一些內(nèi)存,要么將指針視為機(jī)器相關(guān)的。

復(fù)制代碼 代碼如下:

extern int x_int_dummy;    /* in x.c */
#define X_FAIL    (NULL)
#define X_BUSY    (&x_int_dummy)
#define X_FAIL    (NULL)
#define X_BUSY    MD_PTR1  /* MD_PTR1 from "machdep.h" */

    * 浮點(diǎn)數(shù)字既包含精度也包含范圍。這些都是數(shù)據(jù)對象大小無關(guān)的。但是,一個32位浮點(diǎn)數(shù)在不同機(jī)器上溢出時的值有所不同。同時,4.9乘以5.1在不同的機(jī) 器上可能產(chǎn)生兩個不同的數(shù)字。在圓整(rounding)和截?cái)喾矫娴牟町悓⒔o出特別不同的答案。

    * 在一些機(jī)器上,一個雙精度浮點(diǎn)數(shù)在精度或范圍方面可能比一個單精度浮點(diǎn)數(shù)還要低。

    * 在一些機(jī)器上,double值的前半部分可能是一個具有相同值的float類型。千萬不要依賴于此。

    * 提防帶符號字符。例如,在某些VAX系統(tǒng)上,用在表達(dá)式中的字符是符號擴(kuò)展的,但在其他一些機(jī)器上并非如此。對有符號和無符號有依賴的代碼是不可移植的。 例如,如果假設(shè)c是正值,arrayc在c為有符號且為負(fù)值時將無法正常工作。如果你一定要假設(shè)signed或unsigned字符的話,請 用SIGNED或UNSIGNED為其加上注釋。無符號字符的行為可由unsigned char保證。

    * 避免對ASCII做假設(shè)。如果你必須假設(shè),那么請將其記錄下來并本地化。請記住字符很可能用不止8位表示。

    * 大多數(shù)機(jī)器采用2的補(bǔ)碼表示數(shù),但我們在代碼中不應(yīng)該利用這一特點(diǎn)。使用等價移位操作替代算術(shù)運(yùn)算的優(yōu)化尤其值得懷疑。如果必須這么做,那么機(jī)器相關(guān)的代 碼應(yīng)該用#ifdef定義,或者操作應(yīng)該在#ifdef宏判定下執(zhí)行。你應(yīng)該衡量一下使用這種難以理解的代碼所節(jié)省的時間與做代碼移植時找bug 所花費(fèi)的時間相比孰多孰少。

    * 一般情況下,如果字長或值范圍非常重要,應(yīng)該使用typedef定義具有特定大小的類型。大型程序應(yīng)該具有一個統(tǒng)一的頭文件用于提供通用的、大小 (size)敏感的類型的typedef定義,這樣更加便于修改以及在緊急修復(fù)時查找大小敏感的代碼。無符號類型比有符號整型更加編譯器無關(guān)。如 果既可以用16bit也可以用32bit標(biāo)識一個簡單for循環(huán)的計(jì)數(shù)器,我們應(yīng)該使用int。因?yàn)閷τ诋?dāng)前機(jī)器來說,通過整型可以獲取更高效 (自然)的存儲單元。

    * 數(shù)據(jù)對齊也很重要。例如,在不同的機(jī)器上,一個四字節(jié)的整型數(shù)的可能以任意地址作為起始地址,也可能只允許以偶數(shù)地址作為起始地址,或者只能以4的整數(shù)倍 的地址作為起始地址。因此,一個特定的結(jié)構(gòu)體的各個元素在不同的機(jī)器上的偏移量有不同,即使給定的這些元素在所有機(jī)器上的大小相同。事實(shí)上,一個 包含一個32位指針和一個8位字符的結(jié)構(gòu)提在三個不同的機(jī)器上可能有三個不同的大小。作為一個推論,對象指針可能無法自由互換;通過一個指向起始 地址為奇數(shù)地址長度為4個字節(jié)的指針保存一個整型數(shù)有時可以正常工作,但有時則會導(dǎo)致產(chǎn)生core,有些時候靜悄悄地失敗了(在這個過程中會破壞 其他數(shù)據(jù))。在那些不按字節(jié)尋址的機(jī)器上,字符指針更是"事故高發(fā)地區(qū)"。對齊考慮以及加載器的特殊性使得很容易輕率地認(rèn)為兩個連續(xù)聲明的變量在 內(nèi)存中也是連在一起的,或者某個類型的變量已經(jīng)被適當(dāng)對齊并可以用作其他類型變量使用了。

    * 在一些機(jī)器上,諸如VAX(小端),一個字的字節(jié)隨著地址的增加,其重要性提高;而另外一些機(jī)器上,諸如68000(大端),隨著地址的增加,其重要性降 低。字或更大數(shù)據(jù)對象(諸如一個雙精度字)的字節(jié)順序可能并不相同。因此,任何依賴對象內(nèi)從左到右方向位模式的代碼都值得特別細(xì)致的審查。只有當(dāng) 結(jié)構(gòu)體中兩個不同的位字段不被連接以及不被當(dāng)作一個單元時,這些位字段才具備可移植性。事實(shí)上,連接任意兩個變量都是不可移植的行為。

    * 結(jié)構(gòu)體中有一些未使用的空洞。猜想聯(lián)合體用于類型欺騙。尤其是,一個值不應(yīng)該在存儲時使用一個類型,而在讀取時使用另外一種類型。對聯(lián)合體來說,一個顯式 的標(biāo)簽(tag)字段可能會很有用。

    * 不同的編譯器在返回結(jié)構(gòu)體時使用不同的約定。這就會導(dǎo)致代碼在接受從不同編譯器編譯的庫代碼中返回的結(jié)構(gòu)體值時會出現(xiàn)錯誤。結(jié)構(gòu)體指針不是問題。

    * 不要假設(shè)參數(shù)傳遞機(jī)制。特別是指針大小以及參數(shù)求值順序,大小等。例如,下面的代碼就不具備可移植性。

復(fù)制代碼 代碼如下:

        c = foo (getchar(), getchar());

    char
    foo (c1, c2, c3)
    char c1, c2, c3;
    {
        char bar = *(&c1 + 1);
        return (bar);            /* often won't return c2 */
    }

    * 上面的例子有諸多問題。??赡芟蛏显鲩L,也可能向下增長(事實(shí)上,甚至都不需要一個棧)。參數(shù)在傳入時可能被擴(kuò)大,例如一個char可能以int型被傳 入。參數(shù)可能以從左到右,從右到左,或以任意順序壓入棧,或直接放在寄存器中(根本無需壓棧)。參數(shù)求值的順序也可能與壓棧的次序有所不同。一個 編譯器可能使用多種(不兼容的)調(diào)用約定。

    * 在某些機(jī)器上,空字符指針((char *)0)常被當(dāng)作指向空字符串的指針對待。不要依賴于此。

   *  不要修改字符串常量。下面就是一個臭名昭著的例子

復(fù)制代碼 代碼如下:

    s = "/dev/tty??";
  strcpy (&s[8], ttychars);

    * 地址空間可能有空洞。簡單計(jì)算一個數(shù)組中未分配空間的元素(在數(shù)組實(shí)際存儲區(qū)域之前或之后)的地址可能會導(dǎo)致程序崩潰。如果這個地址被用于比較,有時程序 可以運(yùn)行,但會破壞數(shù)據(jù),報(bào)錯,或陷入死循環(huán)。在ANSI C中,指向一個對象數(shù)組的指針指向數(shù)組結(jié)尾后的第一個元素是合法的,這在一些老編譯器上通常是安全的。不過這個"在外邊"不可以被解引用。

    * 只有==和!=比較可用于某給定類型的所有指針。當(dāng)兩個指針指向同一個數(shù)組內(nèi)的元素(或數(shù)組后第一個元素)時,使用<<、<=、& amp; gt;或>=對兩個指針進(jìn)行比較是可移植的。同樣,僅僅對指向同一個數(shù)組內(nèi)的元素(或數(shù)組后第一個元素)的兩個指針使用算術(shù)操作符才是可移 植的。

    * 字長(word size)也影響移位和掩碼。下面代碼在一些68000機(jī)器上只會將一個整型數(shù)的最右三個位清0,而在其他機(jī)器上它還會將高地址的兩個字節(jié)清零。x &= 0177770 使用 x &= ~07可以在所有機(jī)器上正常工作。位字段(bitfield)沒有這些問題。

    * 表達(dá)式內(nèi)的副作用可能導(dǎo)致代碼語義是編譯器相關(guān)的,因?yàn)樵诖蠖鄶?shù)情況下C語言的求值順序是沒有顯式定義的。下面是一個臭名昭著的例子:

復(fù)制代碼 代碼如下:

    a[i] = b[i++];

    在上面的例子中,我們只知道b的下標(biāo)值沒有被增加。a的下標(biāo)i值可能是自增后的值也可能是自增前的值。

復(fù)制代碼 代碼如下:

    struct bar_t { struct bar_t *next; } bar;
    bar->next = bar = tmp;

在第二個例子中,bar->next的地址很可能在bar被賦值之前被計(jì)算使用。

復(fù)制代碼 代碼如下:

bar = bar->next = tmp;

第三個例子中,bar可能在bar->next之前被賦值。雖然這可能有悖于"賦值從右到左處理"的規(guī)則,但這確是一個合法的解析??紤]下 面的例子:

復(fù)制代碼 代碼如下:

long i;
short a[N];
i = old
i = a[i] = new;

賦給i的值必須是一個按照從右到左的處理順序進(jìn)行賦值處理后的值。但是i可能在ai被賦值前而被賦值為"(long) (short)new"。不同編譯器作法不同。

    * 質(zhì)疑代碼中出現(xiàn)的數(shù)值(“魔數(shù)”)。

    * 避免使用預(yù)處理器技巧。一些諸如使用/ /粘和字符串以及依賴參數(shù)字符串展開的宏會破壞代碼可靠性。

復(fù)制代碼 代碼如下:

    #define FOO(string)    (printf("string = %s",(string)))
    …
  FOO(filename);

只是在有些時候會擴(kuò)展為

復(fù)制代碼 代碼如下:

 (printf("filename = %s",(filename)))

小心。詭異的預(yù)處理器在一些機(jī)器上可能導(dǎo)致宏異常中斷。下面是一個宏的兩種不同實(shí)現(xiàn)版本:

復(fù)制代碼 代碼如下:

  #define LOOKUP(chr)    (a['c'+(chr)])    /* Works as intended. */
  #define LOOKUP(c)    (a['c'+(c)])        /* Sometimes breaks. */

第二個版本的LOOKUP可能以兩種不同的方式擴(kuò)展,并且會導(dǎo)致代碼異常中斷。

    * 熟悉現(xiàn)有的庫函數(shù)和定義(但不用太熟悉。與其外部接口相反,庫基礎(chǔ)設(shè)施的內(nèi)部細(xì)節(jié)常會改變并且沒有警告,這些細(xì)節(jié)常常也是不可移植的)。你不應(yīng)該再自己重 新編寫字符串比較例程、終端控制例程或?yàn)橄到y(tǒng)結(jié)構(gòu)編寫你自己的定義。自己動手實(shí)現(xiàn)既浪費(fèi)你的時間,又使得你的代碼可讀性變差,因?yàn)榱硗庖粋€讀者需 要知道你是否在新的實(shí)現(xiàn)中做了什么特殊的事情,并嘗試證實(shí)它們的存在。同時這樣做會使得你無法充分利用一些輔助的微代碼或其他有助于提高系統(tǒng)例程 性能的方法。更進(jìn)一步,它將是一個bug的高產(chǎn)源頭。如果可能的話,要知道公共庫之間的差異(如ANSI、POSIX等等)。

    * 如果lint可用,請使用lint。這個工具對于查找代碼中機(jī)器相關(guān)的構(gòu)造、其他不一致性以及順利通過編譯器檢查的程序bug時具有很高價值。如果你的編 譯器具備打開警告的開關(guān),請打開它。

    * 質(zhì)疑在代碼塊內(nèi)部的與代碼塊外部switch或goto有關(guān)聯(lián)的標(biāo)簽(Label)。

    無論類型在哪里,參數(shù)都應(yīng)該被轉(zhuǎn)換為適當(dāng)?shù)念愋汀.?dāng)NULL用在沒有原型的函數(shù)調(diào)用時,請對NULL進(jìn)行轉(zhuǎn)換。不要讓函數(shù)調(diào)用成為類型欺騙發(fā)生的地方。C 語言的類型提升規(guī)則很是讓人費(fèi)解,所以盡量小心。例如,如果一個函數(shù)接受一個32位長的長整型做為參數(shù),但實(shí)際傳入的卻是一個16位長的整型數(shù), 函數(shù)??赡軙o法對齊,這個值也可能會被錯誤提升。

    * 在混用有符號和無符號值的算術(shù)計(jì)算時請使用顯式類型轉(zhuǎn)換

    * 應(yīng)該謹(jǐn)慎使用跨程序的goto、longjmp。很多實(shí)現(xiàn)"忘記"恢復(fù)寄存器中的值了。盡可能將關(guān)鍵的值聲明為volatile,或?qū)⑺鼈冏⑨尀?VOLATILE。

    * 一些鏈接器將名字轉(zhuǎn)換為小寫,并且一些鏈接器只識別前六個字母作為唯一標(biāo)識。在這些系統(tǒng)上程序可能會悄悄地中斷運(yùn)行。

    * 當(dāng)心編譯器擴(kuò)展。如果使用了編譯器擴(kuò)展,請將他們視為機(jī)器依賴并用文檔記錄下來。

    * 通常程序無法在數(shù)據(jù)段執(zhí)行代碼或者無法將數(shù)據(jù)寫入代碼段。即使程序可以這么做,也無法保證這么做是可靠的。

17. 標(biāo)準(zhǔn)C

現(xiàn)代C編譯器支持一些或全部的ANSI提議的標(biāo)準(zhǔn)C。無論何時可能的話,盡量用標(biāo)準(zhǔn)C編寫和運(yùn)行程序,并且使用諸如函數(shù)原型,常量存儲以及 volatile(易失性)存儲等特性。標(biāo)準(zhǔn)C通過給優(yōu)化器提供有有效的信息以提升程序的性能。標(biāo)準(zhǔn)C通過保證所有編譯器接受同樣的輸入語言以及提供相關(guān) 機(jī)制隱藏機(jī)器相關(guān)內(nèi)容或?qū)τ谀切C(jī)器相關(guān)代碼提供警告的方式提升代碼的可移植性。

17.1 兼容性

編寫很容易移植到老編譯器上的代碼。例如,有條件地在global.h中定義一些新(標(biāo)準(zhǔn)中的)關(guān)鍵字,比如const和volatile。標(biāo)準(zhǔn)編譯器預(yù) 定義了預(yù)處理器符號STDC(見腳注8)。void類型很難簡單地處理正確,因?yàn)楹芏嗬暇幾g器只理解void,但不認(rèn)識void。最簡單的方法就是定義一 個新類型VOIDP(與機(jī)器和編譯器相關(guān)),通常在老編譯器下該類型被定義為char*。

復(fù)制代碼 代碼如下:

#if __STDC__
    typedef void *voidp;
#   define COMPILER_SELECTED
#endif
#ifdef A_TARGET
#    define const
#    define volatile
#    define void int
    typedef char *voidp;
#    define COMPILER_SELECTED
#endif
#ifdef …
    …
#endif
#ifdef COMPILER_SELECTED
#    undef COMPILER_SELECTED
#else
    { NO TARGET SELECTED! }
#endif

注意在ANSI C中,#必須是同一行中預(yù)處理器指示符的第一個非空白字符。在一些老編譯器中,它必須是同一行中的第一個字符。

當(dāng)一個靜態(tài)函數(shù)具有前置聲明時,前置聲明必須包含存儲修飾符。在一些老編譯器中,這個修飾符必須是"extern"。對于ANSI編譯器,這個存儲修飾符 必須為static,但全局函數(shù)依然必須聲明為extern。因此,靜態(tài)函數(shù)的前置聲明應(yīng)該使用一個#define,例如FWD_STATIC,并通 過#ifdef適當(dāng)定義。

一個"#ifdef NAME"應(yīng)該要么以"#endif"結(jié)尾,要么以"#endif / NAME /結(jié)尾,不應(yīng)該用"#endif NAME"結(jié)尾。對于短小的#ifdef不應(yīng)該使用注釋,因?yàn)橥ㄟ^代碼我們可以明確其含義。

ANSI的三字符組可能導(dǎo)致內(nèi)容包含??的字符串的程序神秘的中斷。

17.2 格式化

ANSI C的代碼風(fēng)格與常規(guī)C一樣,但有兩點(diǎn)意外:存儲修飾符(storage qualifiers)和參數(shù)列表。

由于const和volatile的綁定規(guī)則很奇怪,因此每個const或volatile對象都應(yīng)該單獨(dú)聲明。

復(fù)制代碼 代碼如下:

int const *s;        /* YES */
int const *s, *t;    /* NO */

具備原型的函數(shù)將參數(shù)聲明和定義歸并在一個參數(shù)列表中了。應(yīng)該在函數(shù)的注釋中提供各個參數(shù)的注釋。

復(fù)制代碼 代碼如下:

/*
 * `bp': boat trying to get in.
 * `stall': a list of stalls, never NULL.
 * returns stall number, 0 => no room.
 */
int
enter_pier (boat_t const *bp, stall_t *stall)
{
    …

17.3 原型

應(yīng)該使用函數(shù)原型使得代碼更加健壯并且運(yùn)行時性能更好。不幸地是原型的聲明

復(fù)制代碼 代碼如下:

extern void bork (char c);

與定義不兼容。

復(fù)制代碼 代碼如下:

void
bork (c)
char c;
 …

原型中c應(yīng)該以機(jī)器上最自然的類型傳入,很可能是一個字節(jié)。而非原型化(向后兼容)的定義暗示c總是以一個整型傳入。如果一個函數(shù)具有可類型提升的參數(shù), 那么調(diào)用者和被調(diào)用者必須以相等地方式編譯。要么都必須使用函數(shù)原型,要么都不使用原型。如果在程序設(shè)計(jì)時參數(shù)就是可以提升類型的,那么問題就可以被避 免,例如bork可以定義成接受一個整型參數(shù)。

如果定義也是原型化的,上面的聲明將工作正常。

復(fù)制代碼 代碼如下:

void
bork (char c)
{
    …

不幸地是,原型化的語法將導(dǎo)致非ANSI編譯器拒絕這個程序。

但我們可以很容易地通過編寫外部聲明來同時適應(yīng)原型與老編譯器。

復(fù)制代碼 代碼如下:

#if __STDC__
#    define PROTO(x) x
#else
#    define PROTO(x) ()
#endif

extern char **ncopies PROTO((char *s, short times));

注意PROTO必須使用雙層括號。

最后,最好只使用一種風(fēng)格編寫代碼(例如,使用原型)。當(dāng)需要非原型化的版本時,可使用一個自動轉(zhuǎn)換工具生成。

17.4 Pragmas

Pragmas用于以一種可控的方式引入機(jī)器相關(guān)的代碼。很顯然,pragma應(yīng)該被視為機(jī)器相關(guān)的。不幸地是,ANSI pragmas的語法使得我們無法將其隔離到機(jī)器相關(guān)的頭文件中了。

Pragmas分為兩類。優(yōu)化相關(guān)的可以被安全地忽略。而那些影響系統(tǒng)行為(需要pragmas)的Pragmas則不能忽略。需要的pragmas應(yīng)該結(jié)合#ifdef使用,這樣如果一個pragma都沒有選到,編譯過程將退出。

兩個編譯器可能通過兩個不同的方式使用同一個給定的pragma。例如,一個編譯器可能使用haggis發(fā)出一個優(yōu)化信號。而另一個可能使用它暗示一個特 定語句,一旦執(zhí)行到此,程序應(yīng)該退出。不過,一旦使用了pragma,它們必須總是被機(jī)器相關(guān)的#ifdef包圍。對于非ANSI編譯器,Pragmas 必須總是被#ifdef。確保對#pragma的#進(jìn)行縮進(jìn),否則一些較老的預(yù)處理器處理它時會掛起。

復(fù)制代碼 代碼如下:

#if defined(__STDC__) && defined(USE_HAGGIS_PRAGMA)
    #pragma (HAGGIS)
#endif

    "ANSI標(biāo)準(zhǔn)中描述的'#pragma'命令具有任意實(shí)現(xiàn)定義的影響。在GNU C預(yù)處理中,'#pragma'首先嘗試運(yùn)行游戲'rogue';如果失敗,它將嘗試運(yùn)行游戲'hack';如果失敗,它將嘗試運(yùn)行GNU Emacs顯示漢諾塔;如果失敗,它將報(bào)告一個致命錯誤。無論如何,預(yù)處理將不再繼續(xù)。"

    — GNU CC 1.34 C預(yù)處理手冊。

18. 特殊考慮

這節(jié)包含一些雜項(xiàng):‘做'與'不做'。

    * 不要通過宏替換來改變語法。這將導(dǎo)致程序?qū)τ谒腥硕际请y以理解的,除了那個肇事者。

    * 不要在需要離散值的地方使用浮點(diǎn)變量。使用一個浮點(diǎn)數(shù)作為循環(huán)計(jì)數(shù)器無疑是搬起石頭砸自己的腳??偸怯?lt;=或>=測試浮點(diǎn)數(shù),對它們永遠(yuǎn)不要 用精確比較(==或!=)。

    * 編譯器也有bug。常見且高發(fā)的問題包括結(jié)構(gòu)體賦值和位字段。你無法泛泛的預(yù)測一個編譯器都有哪些bug。但你可以在程序中避免使用那些已知的在所有編譯 器上都存在問題的結(jié)構(gòu)。你無法讓你寫的任何代碼都是有用的,你可能仍然會遇到bug,并且在這期間編譯器很可能會被修復(fù)。因此,只有當(dāng)你被強(qiáng)制使 用某個特定的充斥bug的編譯器時,你才應(yīng)該"圍繞"著編譯器bug寫代碼。

    * 不要依賴自動代碼美化工具。良好代碼風(fēng)格的主要受益者就是代碼的編寫者,并且尤其在手寫算法或偽代碼的早期設(shè)計(jì)階段。自動代碼美化工具只應(yīng)該用在那些已經(jīng) 完成、語法正確并且此后不能滿足當(dāng)空白和縮進(jìn)被更為關(guān)注的要求時。伴隨著對細(xì)致程序員的細(xì)節(jié)的關(guān)注,對于那些將函數(shù)或文件布局解釋清楚的工作,程 序員們會做得更好(換句話說,一些視覺布局是由意圖而不是語法決定的,美化工具無法了解到程序員的思想)。粗心的程序員應(yīng)該學(xué)習(xí)成為一個細(xì)致的程 序員,而不是依賴美化工具讓代碼可讀性更好。

    * 意外地遺漏邏輯比較表達(dá)式中的第二個=是一個常犯的問題。使用顯式測試。避免對賦值使用隱式測試。

復(fù)制代碼 代碼如下:

abool = bbool;
if (abool) { …

當(dāng)嵌入的賦值表達(dá)式使用時,確保測試是顯式的,這樣后續(xù)它就無法被"修復(fù)"了。

復(fù)制代碼 代碼如下:

while ((abool = bbool) != FALSE) { …
while (abool = bbool) { …    /* VALUSED */
while (abool = bbool, abool) { …

    顯式地注釋那些在正??刂屏髦獗恍薷牡淖兞浚蚱渌赡茉诰S護(hù)過程中中斷的代碼。

    現(xiàn)代編譯器會自動將變量放到寄存器中。對于你認(rèn)為最關(guān)鍵的變量慎用寄存器。在極端情況下,用寄存器標(biāo)記2-4個最為關(guān)鍵的值,并且將剩余的標(biāo)記為 REGISTER。后者在那些具有較多寄存器的機(jī)器上可以#define為寄存器。

19. Lint

Lint是一個C程序檢查工具,用于檢查C語言源碼文件,探測和報(bào)告諸如類型不兼容、函數(shù)定義與調(diào)用不一致以及潛在的bug等情況。強(qiáng)烈建議在所 有程序上使用lint工具,并且期望大多數(shù)工程將lint作為官方驗(yàn)收程序的一部分。

應(yīng)該注意的是使用lint的最好方法不是將lint作為官方驗(yàn)收之前的一道必須跨過的柵欄,而是作為一個在代碼發(fā)生添加或變更之后使用的工具。 Lint可以發(fā)現(xiàn)一些隱藏的bug并且可以在問題發(fā)生前保證程序的可移植性。lint產(chǎn)生的許多信息確實(shí)暗示了一些事情是錯誤的。一個有意思的故 事是關(guān)于一個漏掉了fprintf的一個參數(shù)的程序:

復(fù)制代碼 代碼如下:

fprintf ("Usage: foo -bar <file>\n");

作者從未有過一個問題。但每當(dāng)一個正常用戶在命令行上犯錯,這個程序就會產(chǎn)生一個core。許多版本的lint工具都能發(fā)現(xiàn)這個問題。

大多l(xiāng)int選項(xiàng)都值得我們學(xué)習(xí)。一些選項(xiàng)可能在合法的代碼上給出警告,但它們也會捕捉到許多把事情搞遭的代碼。注意'–p'只能為庫的一個子 集檢查函數(shù)調(diào)用和類型的一致性,因此程序?yàn)榱俗畲蠡母采w檢查,應(yīng)該同時進(jìn)行帶–p和不帶–p的lint檢查。

Lint也可以識別代碼里的一些特殊注釋。這些注釋可以強(qiáng)制讓lint在發(fā)現(xiàn)問題時關(guān)閉警告輸出,還可以作為一些特殊代碼的文檔。

20. Make

另外一個非常有用的工具是make。在開發(fā)過程中,make只會重新編譯那些上次make后發(fā)生了改變的模塊。它也可以用于自動化其他任務(wù)。一些 常見的約定包括:

復(fù)制代碼 代碼如下:

all
     執(zhí)行所有二進(jìn)制文件的構(gòu)建過程

clean
     刪除所有中間文件

debug
     構(gòu)建一個測試用二進(jìn)制文件a.out或debug

depend
     制作可傳遞的依賴關(guān)系

install
     安裝二進(jìn)制文件,庫等

deinstall
     取消安裝

mkcat
     安裝手冊

lint
    運(yùn)行l(wèi)int工具

print/list
    制作一個所有源文件的拷貝

shar
    為所有源文件制作一個shar文件

spotless
     執(zhí)行make clean,并將源碼存入版本控制工具。注意:不會刪除Makefile,即便它是一個源文件。

source
     撤銷spotless所做的事情。

tags
     運(yùn)行ctags(建議使用-t標(biāo)志)

rdist
     分發(fā)源碼到其他主機(jī)

file.c
     從版本控制系統(tǒng)中檢出這個文件


除此之外,通過命令行也可以定義Makefile使用的值(如"CFLAGS")或源碼中使用的值(如"DEBUG")。

21. 工程相關(guān)的標(biāo)準(zhǔn)

除了這里提到內(nèi)容外,每個獨(dú)立的工程都期望能建立附加標(biāo)準(zhǔn)。下面是每個工程程序管理組需要考慮的問題中的一部分:

    * 哪些額外的命名約定需要遵守?尤其是,那些用于全局?jǐn)?shù)據(jù)的功能歸類以及結(jié)構(gòu)體或聯(lián)合體成員名字的系統(tǒng)化的前綴約定非常有用。

    * 什么樣的頭文件組織適合于工程特定的數(shù)據(jù)體系結(jié)構(gòu)?

    * 應(yīng)該建立什么樣的規(guī)程來審核lint警告?需要確立一個與lint選項(xiàng)一致的寬容度,保證lint不會針對一些不重要的問題給出警告,但同時保證真正的bug或不一致問題不被隱藏。

    * 如果一個工程建立了自己的檔案庫,它應(yīng)該計(jì)劃向系統(tǒng)管理員提供一個lint庫文件。這個lint庫文件允許lint工具檢查對庫函數(shù)的兼容性使用。

    * 需要使用哪種版本控制工具?

22. 結(jié)論

這里描述了一套C語言編程風(fēng)格的標(biāo)準(zhǔn)。其中最重要的幾點(diǎn)是:

    * 合理使用空白和注釋,使得我們通過代碼布局就可以清楚地看出程序的結(jié)構(gòu)。使用簡單表達(dá)式、語句和函數(shù),使他們可以很容易地被理解。

    * 記住,在將來某個時候你或其他人很可能會被要求修改代碼或讓代碼運(yùn)行在一臺不同的機(jī)器上。精心編寫代碼,使得其可以移植到尚不確定的機(jī)器上。局部化你的優(yōu)化,因?yàn)檫@些優(yōu)化經(jīng)常讓人困惑,并且對于該優(yōu)化措施是否適合其他機(jī)器我們持悲觀態(tài)度。

    * 許多風(fēng)格選擇是主觀武斷的。保持代碼風(fēng)格一致比遵循這些絕對的風(fēng)格規(guī)則更重要(尤其是與組織內(nèi)部標(biāo)準(zhǔn)保持一致)?;煊蔑L(fēng)格比任何一種糟糕的風(fēng)格都更加糟糕。

無論采用哪種標(biāo)準(zhǔn),如果認(rèn)為該標(biāo)準(zhǔn)有用就必須遵循它。如果你覺得遵循某條標(biāo)準(zhǔn)時有困難,不要僅僅忽略它們,而是在和你當(dāng)?shù)氐拇髱熁蚪M織內(nèi)的有經(jīng)驗(yàn)的程序員討論后再做決定。

23. 參考資料

B.A. Tague, C Language Portability, Sept 22, 1977. This document issued by department 8234 contains three memos by R.C. Haight, A.L. Glasser, and T.L. Lyon dealing with style and portability.
S.C. Johnson, Lint, a C Program Checker, Unix Supplementary Documents, November 1986.
R.W. Mitze, The 3B/PDP-11 Swabbing Problem, Memorandum for File, 1273-770907.01MF, September 14, 1977.
R.A. Elliott and D.C. Pfeffer, 3B Processor Common Diagnostic Standards- Version 1, Memorandum for File, 5514-780330.01MF, March 30, 1978.
R.W. Mitze, An Overview of C Compilation of Unix User Processes on the 3B, Memorandum for File, 5521-780329.02MF, March 29, 1978.
B.W. Kernighan and D.M. Ritchie, The C Programming Language, Prentice Hall 1978, Second Ed. 1988, ISBN 0-13-110362-8.
S.I. Feldman, Make -- A Program for Maintaining Computer Programs, UNIXSupplementary Documents, November 1986.
Ian Darwin and Geoff Collyer, Can't Happen or / NOTREACHED / or Real Programs Dump Core, USENIX Association Winter Conference, Dallas 1985 Proceedings.
Brian W. Kernighan and P. J. Plauger The Elements of Programming Style. McGraw-Hill, 1974, Second Ed. 1978, ISBN 0-07-034-207-5.
J. E. Lapin Portable C and UNIX System Programming, Prentice Hall 1987, ISBN 0-13-686494-5.
Ian F. Darwin, Checking C Programs with lint, O'Reilly & Associates, 1989. ISBN 0-937175-30-7.
Andrew R. Koenig, C Traps and Pitfalls, Addison-Wesley, 1989. ISBN 0-201-17928-8.

相關(guān)文章

  • C++實(shí)現(xiàn)LeetCode(73.矩陣賦零)

    C++實(shí)現(xiàn)LeetCode(73.矩陣賦零)

    這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(73.矩陣賦零),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C中qsort快速排序使用實(shí)例

    C中qsort快速排序使用實(shí)例

    在學(xué)習(xí)C++ STL的sort函數(shù),發(fā)現(xiàn)C中也存在一個qsort快速排序,要好好學(xué)習(xí)下C的庫函數(shù)啊
    2014-01-01
  • C語言完數(shù)的實(shí)現(xiàn)示例

    C語言完數(shù)的實(shí)現(xiàn)示例

    C語言中的完數(shù)指的是一個正整數(shù),本文主要介紹了C語言完數(shù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • C++ Assert()斷言機(jī)制原理以及使用方法

    C++ Assert()斷言機(jī)制原理以及使用方法

    下面小編就為大家?guī)硪黄狢++ Assert()斷言機(jī)制原理以及使用方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • C語言中對文件最基本的讀取和寫入函數(shù)

    C語言中對文件最基本的讀取和寫入函數(shù)

    這篇文章主要介紹了C語言中對文件最基本的讀取和寫入函數(shù),是C語言入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-08-08
  • C++如何保存bmp圖片

    C++如何保存bmp圖片

    這篇文章主要介紹了C++如何保存bmp圖片問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • C++中套接字庫sockpp的使用詳解

    C++中套接字庫sockpp的使用詳解

    sockpp是一個開源、簡單、現(xiàn)代的C++套接字庫,這篇文章主要為大家詳細(xì)介紹一下套接字庫sockpp的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以學(xué)習(xí)一下
    2023-11-11
  • C++ Boost Heap使用實(shí)例詳解

    C++ Boost Heap使用實(shí)例詳解

    Boost是為C++語言標(biāo)準(zhǔn)庫提供擴(kuò)展的一些C++程序庫的總稱。Boost庫是一個可移植、提供源代碼的C++庫,作為標(biāo)準(zhǔn)庫的后備,是C++標(biāo)準(zhǔn)化進(jìn)程的開發(fā)引擎之一,是為C++語言標(biāo)準(zhǔn)庫提供擴(kuò)展的一些C++程序庫的總稱
    2022-11-11
  • C++?Date類的具體使用(構(gòu)建,重載等)

    C++?Date類的具體使用(構(gòu)建,重載等)

    本文主要介紹了C++?Date類的具體使用(構(gòu)建,重載等),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • c語言顏色代碼詳解

    c語言顏色代碼詳解

    在本篇文章里小編給大家整理的是關(guān)于c語言顏色代碼的知識點(diǎn)內(nèi)容,需要的朋友們可以參考下。
    2020-02-02

最新評論