Recommended C Style and Coding Standards中文翻譯版第2/3頁
6. 空格
o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i---j,i/i);}
- 不光彩的事情,模糊C代碼大賽,1984年。作者要求匿名。
通常情況下,請使用縱向和橫向的空白。縮進(jìn)和空格應(yīng)該反映代碼的塊結(jié)構(gòu)。例如,在一個(gè)函數(shù)定義與下一個(gè)函數(shù)的注釋之間,至少應(yīng)該有兩行空白。
如果一個(gè)條件分支語句過長,那就應(yīng)該將它拆分成若干單獨(dú)的行。
if (foo->next==NULL && totalcount<needed && needed<=MAX_ALLOT
&& server_active(current_input)) { ...
也許下面這樣更好
if (foo->next == NULL
&& totalcount < needed && needed <= MAX_ALLOT
&& server_active(current_input))
{
...
類似地,復(fù)雜的循環(huán)條件也應(yīng)該被拆分為不同行。
for (curr = *listp, trail = listp;
curr != NULL;
trail = &(curr->next), curr = curr->next )
{
...
其他復(fù)雜的表達(dá)式,尤其是那些使用了?:操作符的表達(dá)式,最好也能拆分成多行。
c = (a == b)
? d + f(a)
: f(b) - d;
當(dāng)關(guān)鍵字后面有放在括號內(nèi)的表達(dá)式時(shí),應(yīng)該使用空格將關(guān)鍵字與左括號分隔(sizeof操作符是個(gè)例外)。在參數(shù)列表中,我們也應(yīng)該使用空格顯式 的將各個(gè)參數(shù)隔開。然而,帶有參數(shù)的宏定義一定不能在名字與左括號間插入空格,否則C預(yù)編譯器將無法識別后面的參數(shù)列表。
7. 例子
* Determine if the sky is blue by checking that it isn't night.
* CAVEAT: Only sometimes right. May return TRUE when the answer
* is FALSE. Consider clouds, eclipses, short days.
* NOTE: Uses 'hour' from 'hightime.c'. Returns 'int' for
* compatibility with the old version.
*/
int /* true or false */
skyblue()
{
extern int hour; /* current hour of the day */
return (hour >= MORNING && hour <= EVENING);
}
/*
* Find the last element in the linked list
* pointed to by nodep and return a pointer to it.
* Return NULL if there is no last element.
*/
node_t *
tail(nodep)
node_t *nodep; /* pointer to head of list */
{
register node_t *np; /* advances to NULL */
register node_t *lp; /* follows one behind np */
if (nodep == NULL)
return (NULL);
for (np = lp = nodep; np != NULL; lp = np, np = np->next)
; /* VOID */
return (lp);
}
8. 簡單語句
每行只應(yīng)該有一條語句,除非多條語句關(guān)聯(lián)特別緊密。
case FOO: oogle (zork); boogle (zork); break;
case BAR: oogle (bork); boogle (zork); break;
case BAZ: oogle (gork); boogle (bork); break;
for或while循環(huán)語句的空體應(yīng)該單獨(dú)放在一行并加上注釋,這樣可以清晰的看出空體是有意而為,并非遺漏代碼。
while (*dest++ = *src++)
; /* VOID */
不要對非零表達(dá)式進(jìn)行默認(rèn)測試,例如:
if (f() != FAIL)
比下面的代碼更好
if (f())
即使FAIL的值可能為0(在C中0被認(rèn)為是假)。當(dāng)后續(xù)有人決定使用-1替代0作為失敗返回值時(shí),一個(gè)顯式的測試將解決你的問題。即使比較的值永遠(yuǎn)不會(huì)改變,我們也應(yīng)該使用顯式的比較;例如
if (!(bufsize % sizeof(int)))
應(yīng)該被寫成
if ((bufsize % sizeof(int)) == 0)
這樣可以反映這個(gè)測試的數(shù)值(非布爾)本質(zhì)。一個(gè)常見的錯(cuò)誤點(diǎn)是使用strcmp測試字符串是否相同,這個(gè)測試的結(jié)果永遠(yuǎn)不應(yīng)該被放棄。比較好的方法是定義一個(gè)宏STREQ。
#define STREQ(a, b) (strcmp((a), (b)) == 0)
對謂詞或滿足下面約束的表達(dá)式,非零測試經(jīng)常被放棄:
0表示假,其他都為真。
通過其命名可以看出返回真是顯而易見的。
用isvalid或valid稱呼一個(gè)謂詞,不要用checkvalid。
一個(gè)非常常見的實(shí)踐就是在一個(gè)全局頭文件中聲明一個(gè)布爾類型"bool"。這個(gè)特殊的名字可以極大地提高代碼可讀性。
typedef int bool;
#define FALSE 0
#define TRUE 1
或
typedef enum { NO=0, YES } bool;
即便有了這些聲明,也不要檢查一個(gè)布爾值與1(TRUE,YES等)的相當(dāng)性;可用測試與0(FALSE,NO等)的不等性替代。絕大多數(shù)函數(shù)都可以保證為假的時(shí)候返回0,但為真的時(shí)候只返回非零。
if (func() == TRUE) { ...
必須被寫成
if (func() != FALSE) { ...
如果可能的話,最好為函數(shù)/變量重命名或者重寫這個(gè)表達(dá)式,這樣就可以顯而易見的知道其含義,而無需再與true或false比較了(例如,重命名為isvalid())。
嵌入賦值語句也有用武之地。在一些結(jié)構(gòu)中,在沒有降低代碼可讀性的前提下,沒有比這更好的方式來實(shí)現(xiàn)這個(gè)結(jié)果了。
while ((c = getchar()) != EOF) {
process the character
}
++和--操作符可算作是賦值語句。這樣,為了某些意圖,實(shí)現(xiàn)帶有副作用的功能。使用嵌入賦值語句也可能提高運(yùn)行時(shí)的性能。不過,大家應(yīng)該在提高的性能與下降的可維護(hù)性之間做好權(quán)衡。當(dāng)在一些人為的地方使用嵌入賦值語句時(shí),這種情況會(huì)發(fā)生,例如:
a = b + c;
d = a + r;
不應(yīng)該被下面代碼替代:
d = (a = b + c) + r;
即使后者可能節(jié)省一個(gè)計(jì)算周期。在長期運(yùn)行時(shí),由于優(yōu)化器漸獲成熟,兩者的運(yùn)行時(shí)間差距將下降,而兩者在維護(hù)性方面的差異將提高,因?yàn)槿祟惖挠洃洉?huì)隨著時(shí)間的流逝而衰退。
在任何結(jié)構(gòu)良好的代碼中,goto語句都應(yīng)該保守地使用。使用goto帶來好處最大的地方是從switch、for和while多層嵌套中跳出,但這樣做的需求也暗示了代碼的內(nèi)層結(jié)構(gòu)應(yīng)該被抽取出來放到一個(gè)單獨(dú)的返回值為成功或失敗的函數(shù)中。
for (...) {
while (...) {
...
if (disaster)
goto error;
}
}
...
error:
clean up the mess
當(dāng)需要goto時(shí)候,其對應(yīng)的標(biāo)簽應(yīng)該被放在單獨(dú)一行,并且后續(xù)的代碼縮進(jìn)一級。使用goto語句時(shí)應(yīng)該增加注釋(可能放在代碼塊的頭)以說明它的功用和目的。continue應(yīng)該保守地使用,并且盡可能靠近循環(huán)的頂部。Break的麻煩比較少。
非原型函數(shù)的參數(shù)有時(shí)需要被顯式做類型提升。例如,如果函數(shù)期望一個(gè)32bit的長整型,但卻被傳入一個(gè)16bit的整型數(shù),可能會(huì)導(dǎo)致函數(shù)棧不對齊。指針,整型和浮點(diǎn)值都會(huì)發(fā)生此問題。
9. 復(fù)合語句
復(fù)合語句是一個(gè)由括號括起來的語句列表。有許多種常見的括號格式化方式。如果你有一個(gè)本地標(biāo)準(zhǔn),那請你與本地標(biāo)準(zhǔn)保持一致,或選擇一個(gè)標(biāo)準(zhǔn),并持續(xù)地使用它。在編輯別人的代碼時(shí),始終使用那些代碼中使用的樣式。
control {
statement;
statement;
}
上面的風(fēng)格被稱為"K&R風(fēng)格",如果你還沒有找到一個(gè)自己喜歡的風(fēng)格,那么可以優(yōu)先考慮這個(gè)風(fēng)格。在K&R風(fēng)格中,if-else語句中的else部分以及do-while語句中的while部分應(yīng)該與結(jié)尾大括號在同一行中。而其他大部分風(fēng)格中,大括號都是單獨(dú)占據(jù)一行的。
當(dāng)一個(gè)代碼塊擁有多個(gè)標(biāo)簽時(shí),每個(gè)標(biāo)簽應(yīng)該單獨(dú)放在一行上。必須為C語言的switch語句的fall-through特性(即在代碼段與下一個(gè)case語句之前間沒有break)增加注釋以利于后期更好的維護(hù)。最好是lint風(fēng)格的注釋/指示。
switch (expr) {
case ABC:
case DEF:
statement;
break;
case UVW:
statement;
/*FALLTHROUGH*/
case XYZ:
statement;
break;
}
這里,最后那個(gè)break是不必要的,但卻是必須的,因?yàn)槿绻罄m(xù)另外一個(gè)case添加到最后一個(gè)case的后面時(shí),它將阻止fall-through錯(cuò)誤的發(fā)生。如果使用default case,那么應(yīng)該該default case放在最后,且不需要break,如果它是最后一個(gè)case。
一旦一個(gè)if-else語句在if或else段中包含一個(gè)復(fù)合語句,if和else兩個(gè)段都應(yīng)該用括號括上(稱為全括號(fully bracketed)語法)。
if (expr) {
statement;
} else {
statement;
statement;
}
在如下面那樣的沒有第二個(gè)else的if-if-else語句序列里,括號也是不必可少的。如果ex1后面的括號被省略,編譯器解析將出錯(cuò):
if (ex1) {
if (ex2) {
funca();
}
} else {
funcb();
}
一個(gè)帶else if的if-else語句在書寫上應(yīng)該讓else條件左對齊。
if (STREQ (reply, "yes")) {
statements for yes
...
} else if (STREQ (reply, "no")) {
...
} else if (STREQ (reply, "maybe")) {
...
} else {
statements for default
...
}
這種格式看起來像一個(gè)通用的switch語句,并且縮進(jìn)反映了在這些候選語句間的精確切換,而不是嵌套的語句。
Do-while循環(huán)總是使用括號將循環(huán)體括上。
下面的代碼非常危險(xiǎn):
#ifdef CIRCUIT
# define CLOSE_CIRCUIT(circno) { close_circ(circno); }
#else
# define CLOSE_CIRCUIT(circno)
#endif
...
if (expr)
statement;
else
CLOSE_CIRCUIT(x)
++i;
注意,在CIRCUIT沒有定義的系統(tǒng)上,語句++i僅僅在expr是假的時(shí)候獲得執(zhí)行。這個(gè)例子指出宏用大寫命名的價(jià)值,以及讓代碼完全括號化的價(jià)值。
有些時(shí)候,通過break,continue,goto或return,if可以無條件地進(jìn)行控制轉(zhuǎn)移。else應(yīng)該是隱式的,并且代碼不應(yīng)該縮進(jìn)。
if (level > limit)
return (OVERFLOW)
normal();
return (level);
平坦的縮進(jìn)告訴讀者布爾測試在密封塊的其他部分是保持不變的。
10. 操作符
一元操作符不應(yīng)該與其唯一的操作數(shù)分開。通常,所有其他二元操作符都應(yīng)該使用空白與其操作樹分隔開,但'.'和'->'例外。當(dāng)遇到復(fù)雜表達(dá)式的時(shí)候我們需要做出一些判斷。如果內(nèi)層操作符沒有使用空白分隔而外層使用了,那么表達(dá)式也許會(huì)更清晰些。
如果你認(rèn)為一個(gè)表達(dá)式很難于閱讀,可以考慮將這個(gè)表達(dá)式拆分為多行。在接近中斷點(diǎn)的最低優(yōu)先級操作符處拆分是最好的選擇。由于C具有一些想不到的優(yōu)先級規(guī)則,混合使用操作符的表達(dá)式應(yīng)該使用括號括上。但是過多的括號也會(huì)使得代碼可讀性變差,因?yàn)槿祟惒簧瞄L做括號匹配。
二元逗號操作符也會(huì)被使用到,但通常我們應(yīng)該避免使用它。逗號操作符的最大用途是提供多元初始化或操作,比如在for循環(huán)語句中。復(fù)雜表達(dá)式,例如那些使用了嵌套三元?:操作符的表達(dá)式,可能引起困惑,并且應(yīng)該盡可能的避免使用。三元操作符和逗號操作符在一些使用宏的地方很有用,諸如getchar。在三元操作符?:前的邏輯表達(dá)式的操作數(shù)應(yīng)該被括起來,并且兩個(gè)子表達(dá)式的返回值應(yīng)該是相同類型。
11. 命名約定
毫無疑問,每個(gè)獨(dú)立的工程都有一套自己的命名約定,不過仍然有一些通用的規(guī)則值得參考。
1).為系統(tǒng)用途保留以下劃線開頭或下劃線結(jié)尾的名字,并且這些名字不應(yīng)該被用在任何用戶自定義的名字中。大多數(shù)系統(tǒng)使用這些名字用于用戶不應(yīng) 該也不需知道的名字中。如果你一定要使用你自己私有的標(biāo)識符,可以用標(biāo)識它們歸屬的包的字母作為開頭。
2).#define定義的常量名字應(yīng)該全部大寫。
3).Enum常量應(yīng)該大寫或全部大寫。
4).函數(shù)名、typedef名,變量名以及結(jié)構(gòu)體、聯(lián)合體與枚舉標(biāo)志的名字應(yīng)該用小寫字母。
5).很多"宏函數(shù)"都是全部大寫的。一些宏(諸如getchar和putchar)使用小寫字母命名,這事因?yàn)樗麄兛赡鼙划?dāng)成函數(shù)使用。只有在宏的行為類似一 個(gè)函數(shù)調(diào)用時(shí)才允許小寫命名的宏,也就是說它們只對其參數(shù)進(jìn)行一次求值,并且不會(huì)給具名形式參數(shù)賦值。有些時(shí)候我們無法編寫出一個(gè)具有函數(shù)行為的 宏,即使其參數(shù)也只是求值一次。
6).避免在同一情形下使用不同命名方式,比如foo和Foo。同樣避免foobar和foo_bar這種方式。需要考慮這樣所帶來的困惑。
7).同樣,避免使用看起來相似的名字。在很多終端以及打印設(shè)備上,'I'、'1'和'l'非常相似。給變量命名為l特別糟糕,因?yàn)樗雌饋硎窒癯A?1'。
通常,全局名字(包括enum)應(yīng)該具有一個(gè)統(tǒng)一的前綴,通過該前綴名我們可以識別出這個(gè)名字歸屬于哪個(gè)模塊。全局變量可以選擇匯集在一個(gè)全局結(jié) 構(gòu)中。typedef的名字通常在結(jié)尾加一個(gè)'t'。
避免名字與各種標(biāo)準(zhǔn)庫中的名字沖突。一些系統(tǒng)可能包含一些你所不需要的庫。另外你的程序?qū)砟程旌芸赡芤惨獢U(kuò)展。
12. 常量
數(shù)值型常量不應(yīng)該被硬編碼到源文件中。應(yīng)該使用C預(yù)處理器的#define特性為常量賦予一個(gè)有意義的名字。符號化的常量可以讓代碼具有更好的可讀性。在一處地方統(tǒng)一定義這些值也便于進(jìn)行大型程序的管理,這樣常量值可以在一個(gè)地方進(jìn)行統(tǒng)一修改,只需修改define的值即可。枚舉數(shù)據(jù)類型更適合聲明一組具有離散值的變量,并且編譯器還可以對其進(jìn)行額外的類型檢查。至少,任何硬編碼的值常量必須具有一段注釋,以說明該值的來歷。
常量的定義應(yīng)該與其使用是一致的;例如使用540.0作為一個(gè)浮點(diǎn)數(shù),而不是使用540外加一個(gè)隱式的float類型轉(zhuǎn)換。有些時(shí)候常量0和1被直接使用而沒有用define進(jìn)行定義。例如,一個(gè)for循環(huán)語句中用于標(biāo)識數(shù)組下標(biāo)的常量,
for (i = 0; i < ARYBOUND; i++)
上面代碼是合理的,但下面代碼
door_t *front_door = opens(door[i], 7);
if (front_door == 0)
error("can't open %s\\\\n", door[i]);
是不合理的。在最后的那個(gè)例子中,front_door是一個(gè)指針。當(dāng)一個(gè)值是指針的時(shí)候,它應(yīng)該與NULL比較而不是與0比較。NULL被定義在標(biāo)準(zhǔn)I/O庫頭文件stdio.h中,在一些新系統(tǒng)中它在stdlib.h中定義。即使像1或0這樣的簡單值,我們最好也用define定義成TRUE和FALSE定義后再使用(有些時(shí)候,使用YES和NO可讀性更好)。
簡單字符常量應(yīng)該被定義成字面值,不應(yīng)該使用數(shù)字。不鼓勵(lì)使用非可見文本字符,因?yàn)樗鼈兪遣豢梢浦驳摹H绻强梢娢谋咀址直匾?,尤其是?dāng)它們在字符串中使用時(shí),它們應(yīng)該定義成三個(gè)八進(jìn)制數(shù)字的轉(zhuǎn)義字符(例如: '\007‘)而非一個(gè)字符。即使這樣,這種用法也應(yīng)該考慮其機(jī)器相關(guān)性,并按這里的方法處理。
13. 宏
復(fù)雜表達(dá)式可能會(huì)被用作宏參數(shù),這可能會(huì)因操作符優(yōu)先級順序而引發(fā)問題,除非宏定義中所有參數(shù)出現(xiàn)的位置都用括號括上了。對這種因參數(shù)內(nèi)副作用而引發(fā)的問題,我們似乎也無能為例,除了在編寫表達(dá)式時(shí)杜絕副作用(無論如何,這都是一個(gè)很好的主意)。如果可能的話,盡量在宏定義中對宏參數(shù)只進(jìn)行一次求值。有很多時(shí)候我們無法寫出一個(gè)可像函數(shù)一樣使用的宏。
一些宏也當(dāng)成函數(shù)使用(例如,getc和fgetc)。這些宏會(huì)被用于實(shí)現(xiàn)其他函數(shù),這樣一旦宏自身發(fā)生變化,使用該宏的函數(shù)也會(huì)受到影響。在交換宏和函數(shù)時(shí)務(wù)必要小心,因?yàn)楹瘮?shù)參數(shù)是按值傳遞的,而宏參數(shù)則是通過名稱替換。只有在宏定義時(shí)特別謹(jǐn)慎小心,才有可能減少使用宏時(shí)的擔(dān)心。
宏定義中應(yīng)該避免使用全局變量,因?yàn)槿肿兞康拿趾芸赡鼙痪植柯暶髡谏w。對于那些對具名參數(shù)進(jìn)行修改(不是這些參數(shù)所指向的存儲區(qū)域)或被用作賦值語句左值的宏,我們應(yīng)該添加相應(yīng)的注釋以給予提醒。那些不帶參數(shù)但引用變量,或過長或作為函數(shù)別名的宏應(yīng)該使用空參數(shù)列表,例如:
#define OFF_A() (a_global+OFFSET)
#define BORK() (zork())
#define SP3() if (b) { int x; av = f (&x); bv += x; }
宏節(jié)省了函數(shù)調(diào)用和返回的額外開銷,但當(dāng)一個(gè)宏過長時(shí),函數(shù)調(diào)用和返回的額外開銷就變得微不足道了,這種情況下我們應(yīng)該使用函數(shù)。
在一些情況下,讓編譯器確保宏在使用時(shí)應(yīng)該以分號結(jié)尾是很有必要的。
if (x==3)
SP3();
else
BORK();
如果省略SP3調(diào)用后面的分號,后面的else將會(huì)匹配到SP3宏中的那個(gè)if。有了分號,else分支就不會(huì)與任何if匹配。SP3宏可以這樣安全地實(shí)現(xiàn):
#define SP3() \\\\
do { if (b) { int x; av = f (&x); bv += x; }} while (0)
手工給宏定以加上do-while包圍看起來很別扭,而且很多編譯器和工具會(huì)抱怨在while條件是一個(gè)常量值。一個(gè)用來聲明語句的宏可以使得編碼更加容易:
#ifdef lint
static int ZERO;
#else
# define ZERO 0
#endif
#define STMT( stuff ) do { stuff } while (ZERO)
我們可以用下面代碼來聲明SP3宏:
#define SP3() \\\\
STMT( if (b) { int x; av = f (&x); bv += x; } )
使用STMT宏可以有效阻止一些可以潛在改變程序行為的打印排版錯(cuò)誤。
除了類型轉(zhuǎn)換、sizeof以及上面那些技巧和手法,只有當(dāng)整個(gè)宏用括號括上時(shí)才應(yīng)該包含關(guān)鍵字。
14. 條件編譯
條件編譯在處理機(jī)器依賴、調(diào)試以及編譯階段設(shè)定特定選項(xiàng)時(shí)十分有用。不過要小心條件編譯。各種控制很容易以一種無法預(yù)料的方式結(jié)合在一起。如果使用#ifdef判斷機(jī)器依賴,請確保當(dāng)沒有機(jī)器類型適配時(shí),返回一個(gè)錯(cuò)誤,而不是使用默認(rèn)機(jī)器類型(使用#error并縮進(jìn)一級,這樣它可以一些老舊的編譯器下工作)。如果你#ifdef優(yōu)化選項(xiàng),默認(rèn)情況下應(yīng)該是一個(gè)未經(jīng)優(yōu)化的代碼,而不是一個(gè)不兼容的程序。確保測試的是未經(jīng)優(yōu)化的代碼。
注意在#ifdef區(qū)域內(nèi)的文本可能會(huì)被編譯器掃描(處理),即使#ifdef求值的結(jié)果為假。但即使文件的#ifdef部分永遠(yuǎn)不能被編譯到(例如,#ifdef COMMENT),這部分也不該隨意的放置文本。
盡可能地將#ifdefs放在頭文件中,而不是源文件中。使用#ifdef定義可以在源碼中統(tǒng)一使用的宏。例如,一個(gè)用于檢查內(nèi)存分配的頭文件可能這樣實(shí)現(xiàn):(省略了REALLOC和FREE):
#ifdef DEBUG
extern void *mm_malloc();
# define MALLOC(size) (mm_malloc(size))
#else
extern void *malloc();
# define MALLOC(size) (malloc(size))
#endif
條件編譯通常應(yīng)該基于一個(gè)接一個(gè)的特性的。多數(shù)情況下,都應(yīng)該避免使用機(jī)器或操作系統(tǒng)依賴。
#ifdef BSD4
long t = time ((long *)NULL);
#endif
上面代碼之所以糟糕有兩個(gè)原因:很可能在某個(gè)4BSD系統(tǒng)上有更好的選擇,并且也可能存在在某個(gè)非4BSD系統(tǒng)中上述代碼是最佳代碼。我們可以通過定義諸如TIME_LONG和TIME_STRUCTD等宏作為替代,并且在諸如config.h的配置文件中定義一個(gè)合適的宏。
15. 調(diào)試
"C代碼。C代碼運(yùn)行。運(yùn)行,代碼,運(yùn)行... 請運(yùn)行!!!" -- Barbara Tongue
如果你使用枚舉,第一個(gè)枚舉常量應(yīng)該是一個(gè)非零值,或者第一個(gè)常量應(yīng)該指示一個(gè)錯(cuò)誤。
enum { STATE_ERR, STATE_START, STATE_NORMAL, STATE_END } state_t;
enum { VAL_NEW=1, VAL_NORMAL, VAL_DYING, VAL_DEAD } value_t;
未初始化的值后續(xù)將會(huì)自己獲取。
檢查所有錯(cuò)誤返回值,即使是那些"不能"失敗的函數(shù)的返回值??紤]即使之前所有的文件操作都已經(jīng)成功了,close()和fclose也可能失敗。編寫你自己的函數(shù),使得它們以一種明確的方式測試錯(cuò)誤、返回錯(cuò)誤碼或從程序中退出。包含大量調(diào)試和錯(cuò)誤檢查代碼,并把其中大多數(shù)留在最終的產(chǎn)品中。甚至檢查那些"不可能"的錯(cuò)誤。
使用assert機(jī)制保證傳給每個(gè)函數(shù)的值都是定義明確的,并且中間結(jié)果是形式良好的。
盡可能少的在調(diào)試代碼中使用#ifdef。例如,如果mm_malloc是一個(gè)調(diào)試用的內(nèi)存分配器,那么MALLOC將挑選合適的分配器,避免使用#ifdef在代碼中堆砌垃圾,并且使得分配之間的差異變得清晰,只是在調(diào)試期會(huì)分配些額外內(nèi)存。
#ifdef DEBUG
# define MALLOC(size) (mm_malloc(size))
#else
# define MALLOC(size) (malloc(size))
#endif
對那些"不可能"溢出的對象做邊界校驗(yàn)。一個(gè)向變長存儲區(qū)寫入的函數(shù)應(yīng)該接受一個(gè)參數(shù)maxsize,該參數(shù)即目標(biāo)內(nèi)存區(qū)域的大小。如果有時(shí)候目標(biāo)內(nèi)存區(qū)域大小未知,一些maxsize的"魔數(shù)"值應(yīng)該意味著"沒有邊界檢查"。當(dāng)邊界檢查失敗,請確保這個(gè)函數(shù)做一些有用的事情,諸如退出程序或返回一個(gè)錯(cuò)誤狀態(tài)。
/*
* INPUT: A null-terminated source string `src' to copy from and
* a `dest' string to copy to. `maxsize' is the size of `dest'
* or UINT_MAX if the size is not known. `src' and `dest' must
* both be shorter than UINT_MAX, and `src' must be no longer than
* `dest'.
* OUTPUT: The address of `dest' or NULL if the copy fails.
* `dest' is modified even when the copy fails.
*/
char *
copy (dest, maxsize, src)
char *dest, *src;
unsigned maxsize;
{
char *dp = dest;
while (maxsize\-\- > 0)
if ((*dp++ = *src++) == '\\\\0')
return (dest);
return (NULL);
}
總之,記住一個(gè)程序產(chǎn)生錯(cuò)誤答案的速度快兩倍(譯注:是否有南轅北轍的意味),實(shí)則是變得無限緩慢,這個(gè)道理對那些偶爾崩潰或打擊有效數(shù)據(jù)的程序同樣成立。
相關(guān)文章
C++實(shí)現(xiàn)LeetCode(73.矩陣賦零)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(73.矩陣賦零),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07