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

Recommended C Style and Coding Standards中文翻譯版

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

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

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

    * 組織源文件時(shí)將機(jī)器無關(guān)與機(jī)器相關(guān)的代碼分別放在不同文件中。之后如果這個(gè)程序需要被移植到一個(gè)新機(jī)器上時(shí),我們就可以很容易判斷出來哪些需要被改變。為 一些文件的頭文件中機(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ī)器針對某一類型可能有不止一個(gè)大小。其類型大小取決于編譯器和不同的編譯期標(biāo)志。下面表展示了大多數(shù)系統(tǒng)的"安全"類型大小。無符號(hào)與帶符 號(hào)數(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類型可以保證有足夠位精度來表示一個(gè)指向任意數(shù)據(jù)對象的指針。void()()類型可以保證表示一個(gè)指向任意函數(shù)的指針。當(dāng)你需要通用指針時(shí) 可以使用這些類型(在一些舊的編譯器里,分別用char和char()()表示)。確保在使用這些指針類型之前將其轉(zhuǎn)換回正確的類型。

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

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

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

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

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

    * 對于ANSI編譯器,當(dāng)兩個(gè)類型相同的指針訪問同一塊存儲(chǔ)區(qū)時(shí),則它們比較是相等的。當(dāng)一個(gè)非0整型常量被轉(zhuǎn)換為指針類型時(shí),它們可能與其他指針相等。對 于非ANSI編譯器,訪問同一塊存儲(chǔ)區(qū)的兩個(gè)指針比較可能并不相同。例如,下面兩個(gè)指針比較可能相等或不相等,并且他們可能或可能沒有訪問同一塊 存儲(chǔ)區(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)的。但是,一個(gè)32位浮點(diǎn)數(shù)在不同機(jī)器上溢出時(shí)的值有所不同。同時(shí),4.9乘以5.1在不同的機(jī) 器上可能產(chǎn)生兩個(gè)不同的數(shù)字。在圓整(rounding)和截?cái)喾矫娴牟町悓⒔o出特別不同的答案。

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

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

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

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

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

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

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

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

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

    * 不同的編譯器在返回結(jié)構(gòu)體時(shí)使用不同的約定。這就會(huì)導(dǎo)致代碼在接受從不同編譯器編譯的庫代碼中返回的結(jié)構(gòu)體值時(shí)會(huì)出現(xiàn)錯(cuò)誤。結(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í)上,甚至都不需要一個(gè)棧)。參數(shù)在傳入時(shí)可能被擴(kuò)大,例如一個(gè)char可能以int型被傳 入。參數(shù)可能以從左到右,從右到左,或以任意順序壓入棧,或直接放在寄存器中(根本無需壓棧)。參數(shù)求值的順序也可能與壓棧的次序有所不同。一個(gè) 編譯器可能使用多種(不兼容的)調(diào)用約定。

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

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

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

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

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

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

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

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

復(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;

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

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

bar = bar->next = tmp;

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

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

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

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

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

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

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

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

只是在有些時(shí)候會(huì)擴(kuò)展為

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

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

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

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

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

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

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

    * 如果lint可用,請使用lint。這個(gè)工具對于查找代碼中機(jī)器相關(guān)的構(gòu)造、其他不一致性以及順利通過編譯器檢查的程序bug時(shí)具有很高價(jià)值。如果你的編 譯器具備打開警告的開關(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)用時(shí),請對NULL進(jìn)行轉(zhuǎn)換。不要讓函數(shù)調(diào)用成為類型欺騙發(fā)生的地方。C 語言的類型提升規(guī)則很是讓人費(fèi)解,所以盡量小心。例如,如果一個(gè)函數(shù)接受一個(gè)32位長的長整型做為參數(shù),但實(shí)際傳入的卻是一個(gè)16位長的整型數(shù), 函數(shù)棧可能會(huì)無法對齊,這個(gè)值也可能會(huì)被錯(cuò)誤提升。

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

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

    * 一些鏈接器將名字轉(zhuǎn)換為小寫,并且一些鏈接器只識(shí)別前六個(gè)字母作為唯一標(biāo)識(shí)。在這些系統(tǒng)上程序可能會(huì)悄悄地中斷運(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。無論何時(shí)可能的話,盡量用標(biāo)準(zhǔn)C編寫和運(yùn)行程序,并且使用諸如函數(shù)原型,常量存儲(chǔ)以及 volatile(易失性)存儲(chǔ)等特性。標(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ù)處理器符號(hào)STDC(見腳注8)。void類型很難簡單地處理正確,因?yàn)楹芏嗬暇幾g器只理解void,但不認(rèn)識(shí)void。最簡單的方法就是定義一 個(gè)新類型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ù)處理器指示符的第一個(gè)非空白字符。在一些老編譯器中,它必須是同一行中的第一個(gè)字符。

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

一個(gè)"#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)意外:存儲(chǔ)修飾符(storage qualifiers)和參數(shù)列表。

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

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

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

具備原型的函數(shù)將參數(shù)聲明和定義歸并在一個(gè)參數(shù)列表中了。應(yīng)該在函數(shù)的注釋中提供各個(gè)參數(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)行時(shí)性能更好。不幸地是原型的聲明

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

extern void bork (char c);

與定義不兼容。

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

void
bork (c)
char c;
 …

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

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

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

void
bork (char c)
{
    …

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

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

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

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

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

注意PROTO必須使用雙層括號(hào)。

最后,最好只使用一種風(fēng)格編寫代碼(例如,使用原型)。當(dāng)需要非原型化的版本時(shí),可使用一個(gè)自動(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使用,這樣如果一個(gè)pragma都沒有選到,編譯過程將退出。

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

復(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)告一個(gè)致命錯(cuò)誤。無論如何,預(yù)處理將不再繼續(xù)。"

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

18. 特殊考慮

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

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

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

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

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

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

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

abool = bbool;
if (abool) { …

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

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

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

    顯式地注釋那些在正常控制流之外被修改的變量,或其他可能在維護(hù)過程中中斷的代碼。

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

19. Lint

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

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

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

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

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

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

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

20. Make

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

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

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

clean
     刪除所有中間文件

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

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

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

deinstall
     取消安裝

mkcat
     安裝手冊

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

print/list
    制作一個(gè)所有源文件的拷貝

shar
    為所有源文件制作一個(gè)shar文件

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

source
     撤銷spotless所做的事情。

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

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

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


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

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

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

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

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

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

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

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

22. 結(jié)論

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

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

    * 記住,在將來某個(gè)時(shí)候你或其他人很可能會(huì)被要求修改代碼或讓代碼運(yùn)行在一臺(tái)不同的機(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)時(shí)有困難,不要僅僅忽略它們,而是在和你當(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中也存在一個(gè)qsort快速排序,要好好學(xué)習(xí)下C的庫函數(shù)啊
    2014-01-01
  • C語言完數(shù)的實(shí)現(xiàn)示例

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

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

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

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

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

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

    C++如何保存bmp圖片

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

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

    sockpp是一個(gè)開源、簡單、現(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庫是一個(gè)可移植、提供源代碼的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í)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • c語言顏色代碼詳解

    c語言顏色代碼詳解

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

最新評(píng)論