這篇短小的文檔用于描述linux內(nèi)核編程中推薦的編程風(fēng)格。編程風(fēng)格是很個人
化的東西,我不想把我的觀點_強加_給任何人,但這是我必須維護(hù)的代碼中所遵守
的,我也建議其他部分的代碼也能遵守它。請至少給這里的觀點一些考慮。
首先,我建議你打印一份GNU代碼風(fēng)格,不是去讀它,而是把它燒了,這是個很
不錯的姿態(tài)。
不廢話了,下面就是Linux內(nèi)核編程風(fēng)格:
第一章:縮進(jìn)
制表符(tabs)占8個字符,所以縮進(jìn)也是8個字符。有些異端運動想使用4個字符
(甚至是2個字符)的縮進(jìn),這和把PI(圓周率)定為3沒什么兩樣。
原因:縮進(jìn)的根本目的是用來清晰地標(biāo)識一個控制塊的起始。特別是當(dāng)你連續(xù)盯
著屏幕看了20
個小時后,你就會體會到更長的縮進(jìn)的好處了。
現(xiàn)在,有些人提出8字符縮進(jìn)會使得代碼太偏向右邊,當(dāng)使用80字符的終端
時很難閱讀。答案是如果你需要三層以上的縮進(jìn),那么你已經(jīng)完蛋了,應(yīng)該改改
你的程序了。
簡而言之,8字符縮進(jìn)使得閱讀代碼更為容易,并且在你的縮進(jìn)層次過深時提出
警告。應(yīng)該留心這樣的警告。
第二章:括號的位置
括號位置的問題在C編程風(fēng)格中經(jīng)常被提出。和縮進(jìn)大小不同,括號位置的選擇
并沒有太多技術(shù)上的原因,而更多的是個人的喜好。比如Kernighan和Ritchie的
弟子們把左括號放在一行的最后,把右括號放在一行的開始,象這樣:
if (x is true) {
we do y
}
但是,函數(shù)是一種特殊的情況,函數(shù)的左括號放在下一行的開始,象這樣:
int function(int x)
{
body of function
}
全世界的異端人士指出這種不一致的做法 ...嗯...
不太一致,但是所有思維正
確的人知道 (a) K&R是_對_的 (b)
K&R是對的。而且,函數(shù)確實是特殊的(你在C
中無法對函數(shù)進(jìn)行嵌套)。
注意到右括號完全占有單獨的一行,_除非_當(dāng)它后面還有未完成的語句,比如do
語句中的“while”或者if語句中的“else”,想這樣:
do {
body of do-loop
} while (condition);
和
if (x == y) {
..
} else if (x > y) {
...
} else {
....
}
原因:K&R。
還有,注意到這種括號的布局方法還減少了空行(或者說是幾乎是空行)的數(shù)目,
而且沒有減小可讀性。因為你屏幕上的空行是不可回收資源(這里想一下25行的
終端屏幕),這樣你會有更多的空行用于加注釋。
第三章:命名
C是個斯巴達(dá)式(崇尚簡潔風(fēng)格的)語言,所以你的命名方法也應(yīng)該如此。與
Modula-2和Pascal程序員不同,C程序員不使用
ThisVariableIsATemporaryCounter這樣可愛的名字。一個C程序員會把一個變量
叫做“tmp”,這樣的變量名更容易寫,而且理解起來也不算太難。
_但是_,盡管人人都會對大小寫混雜的名字皺眉頭,全局變量名則必須如此。管
一個全局函數(shù)叫“foo”是故意找岔。
_全局_變量(只有在_真正_需要時才使用)需要有個描述性強的名字,這點和全
局函數(shù)一樣。如果你有個函數(shù)用于對活躍用戶進(jìn)行計數(shù),你嬰兒叫它
“count_active_users()”,而不是“cntusr()”。
把函數(shù)的類型加入到名字中(所謂的匈牙利命名法)是腦損傷的表現(xiàn)
- 編譯器
知道類型,能夠?qū)λM(jìn)行檢查,這種命名法只會把程序員自己搞暈。難怪微軟做
了那么多充滿bug的程序。
_局部_變量應(yīng)該短小扼要。如果你有個隨機的整數(shù)循環(huán)變量,可能最好叫它“i”。
把它叫做“l(fā)oop_counter”是效率低下的,在不會發(fā)生混淆的情況下。類型地,
“tmp”可以被用于任何類型的存儲臨時值的變量。
如果你擔(dān)心混淆你的局部變量,那么你就會有另一個問題,所謂的函數(shù)膨脹荷爾
蒙失衡綜合癥,請看下一章。
第四章:函數(shù)
函數(shù)應(yīng)該短小而甜美,而且只能做一件事。他們應(yīng)該只用一兩屏幕(我們都知道,
ISO/ANSI標(biāo)準(zhǔn)屏幕大小是80x24)就能裝下,只做并且做好一件事。
函數(shù)的最大長度應(yīng)該與函數(shù)的復(fù)雜性和縮進(jìn)層次成反比。所以,如果你有個只有
一個很長(但很簡單)的case語句的函數(shù),對許多case做一些很少的操作,那么
這個函數(shù)長點也沒有關(guān)系。
但是,如果你有一個復(fù)雜的函數(shù),你擔(dān)心一個中等智力的高一學(xué)生可能無法理解,
那么你應(yīng)該更嚴(yán)格地遵守最大長度限制。使用有描述性名字的幫助函數(shù)(你可以
讓編譯器in-line這些幫助函數(shù),如果你認(rèn)為性能很重要的話,而且編譯器恐怕
會比你做的要好)。
函數(shù)的另一個指標(biāo)是局部變量的數(shù)目,局部變量的數(shù)目不應(yīng)超過5-10個,否則一
定是哪里有問題了。再設(shè)計一下這個函數(shù),把它分解得更小一些。人的大腦一般
可以同時跟蹤7個不同的東西,超過了7個就會暈菜。雖然你很聰明,不過可能你
有時會想理解一下兩星期前所寫的代碼。
第五章:注釋
注釋是好東西,不過存在過分注釋的危險。_永遠(yuǎn)_不要在注釋中解釋你的代碼是
如何工作的:更好的做法是寫出工作方式顯而易見的代碼,解釋糟糕的代碼是浪
費時間。
一般來說,注釋應(yīng)該說明代碼在做什么,而不是怎么做。并且,不要把注釋加在
函數(shù)主體中:如果函數(shù)太復(fù)雜以至于必須對各個部分進(jìn)行注釋,那么你可能要再
去讀讀第四章。你可以加入一些短小的注釋來提醒或警告一些聰明(或難看)的
做法,但不要太過度。更好的選擇是,把注釋放在函數(shù)頭,說明函數(shù)在做什么,
可能還包括它為什么做。
第六章:你的代碼亂七八糟
沒什么,我們都遇到過。你可能從老Unix用戶那里聽說過“GNU
emacs”會自動
對齊C源代碼,但缺省的設(shè)置不是很好(事實上,缺省設(shè)置比胡亂敲打還糟糕
-
一群使用GNU emacs猴子永遠(yuǎn)不會做出漂亮的程序)。
所以,你或者徹底仍掉GNU
emacs,或者采用更理智的設(shè)置。如果選擇后者,你
可一把下面的代碼加到你的.emacs文件中:
(defun linux-c-mode ()
"C mode with adjusted defaults for use with the Linux kernel."
(interactive)
(c-mode)
(c-set-style "K&R")
(setq c-basic-offset 8))
這會定義 M-x linux-c-mode
命令。當(dāng)編寫Linux模塊時,如果你把字符串“-*-
linux-c
-*-”放在文件的頭兩行中,這個模式就會被自動激活。還有,如果你
想在編輯/usr/src/linux目錄下的源文件時linux-c-mode被自動激活,你在你的.
emacs文件中需要加入
(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\\.[ch]$" .
linux-c-mode)
auto-mode-alist))
但是即使你用不了emacs,并不是世界末日:你還可以使用“indent”。
又一次,GNU indent使用了和GNU
emacs一樣的腦死亡設(shè)置,所以你需要給它一
些命令行選項。但是,這不算太壞,因為即使是GNU
indent的作者們也意識到了
K&R的權(quán)威性(GNU的人也不是魔鬼,他們只是在這件事上被誤導(dǎo)了),所以你可
以使用選項“-kr
-i8”(表示“K&R,8字符縮進(jìn)”)運行indent。
“indent”有很多選項,特別是注釋布局部分,你可能想看看它的man手冊。但
是請記住:“indent”不能修改糟糕的程序。
第七章:配置文件
配置選項
(arch/xxx/config.in,以及所有Config.in文件)使用了有些不同的
縮進(jìn)方式。
代碼中使用的是3字符縮進(jìn),config-選項中應(yīng)該使用2字符縮進(jìn)標(biāo)識依賴關(guān)系。
后者只應(yīng)用于bool/tristat選項。對于其他選項,采用你認(rèn)為最合適的縮進(jìn)方式
就可以了。例如:
if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
tristate 'Apply nitroglycerine inside the keyboard (DANGEROUS)'
CONFIG_BOOM
if [ "$CONFIG_BOOM" != "n" ]; then
bool ' Output nice messages when you explode' CONFIG_CHEER
fi
fi
一般來說,所有不穩(wěn)定的選項應(yīng)該標(biāo)為CONFIG_EXPERIMENTAL。所有可能損壞數(shù)
據(jù)的的選項應(yīng)該標(biāo)為(DANGEROUS),其他的試驗選項應(yīng)該標(biāo)為(EXPERIMENTAL)。
第八章:數(shù)據(jù)結(jié)構(gòu)
供多線程使用的數(shù)據(jù)結(jié)構(gòu)應(yīng)該采用引用計數(shù)(reference
counts)。在內(nèi)核中,
垃圾回收(garbage
collection)是不存在的(內(nèi)核之外的垃圾回收效率不高),
這意味著你_必須_使用引用計數(shù)。
引用計數(shù)的使用能避免鎖的使用,使不同的用戶能夠并行使用數(shù)據(jù)結(jié)構(gòu)
- 不需
要擔(dān)心結(jié)構(gòu)會因為睡眠而突然消失。
注意到加鎖_不是_引用計數(shù)的替代物。加鎖用于保證數(shù)據(jù)結(jié)構(gòu)的完整性,而引用
計數(shù)是一個內(nèi)存管理技術(shù)。通常你兩個都需要,不應(yīng)該有任何混淆不清的地方。
一些數(shù)據(jù)結(jié)構(gòu)可能使用兩層的引用計數(shù),當(dāng)對不同的“類”都有使用的時候。子
類的計數(shù)統(tǒng)計所有子類用戶的數(shù)目,當(dāng)子類的計數(shù)為零時只對總計數(shù)減一。
這種“多層引用計數(shù)”的例子可以在內(nèi)存管理代碼(“struct
mm_struct”:
mm_users和mm_cout)和文件系統(tǒng)代碼(“struct
super_block”:s_count和
s_active)中找到。
記住:如果另一個線程能夠看見你的數(shù)據(jù)結(jié)構(gòu),而你卻沒有對它使用引用計數(shù),
那么幾乎可以肯定會有bug存在。