K&R編碼規範(linux核心編碼規範)

來源:互聯網
上載者:User
這是Documentation/CodingStyle 的中文版,似乎沒有LKD裡面講的幽默。再次fuck匈牙利命名法。

“在函數名中包含函數類型(所謂的匈牙利命名法)是腦子出了問題——編譯器知道那些類型而
且能夠檢查那些類型,這樣做只能把程式員弄糊塗了。難怪微軟總是製造出有問題的程式。”

Linux核心代碼風格

這是一個簡短的文檔,描述了linux核心的首選代碼風格。代碼風格是因人而異的,而且我
不願意把我的觀點強加給任何人,不過這裡所講述的是我必須要維護的代碼所遵守的風格,
並且我也希望絕大多數其他代碼也能遵守這個風格。請在寫代碼時至少考慮一下本文所述的
風格。

首先,我建議你列印一份GNU代碼規範,然後不要讀它。燒了它,這是一個具有重大象徵性
意義的動作。

不管怎樣,現在我們開始:

第一章:縮排

定位字元是8個字元,所以縮排也是8個字元。有些異端運動試圖將縮排變為4(乃至2)個字元
深,這幾乎相當於嘗試將圓周率的值定義為3。

理由:縮排的全部意義就在於清楚的定義一個控制塊起止於何處。尤其是當你盯著你的螢幕
連續看了20小時之後,你將會發現大一點的縮排會使你更容易分辨縮排。

現在,有些人會抱怨8個字元的縮排會使代碼向右邊移動的太遠,在80個字元的終端螢幕上
就很難讀這樣的代碼。這個問題的答案是,如果你需要3級以上的縮排,不管用何種方式你
的代碼已經有問題了,應該修正你的程式。

簡而言之,8個字元的縮排可以讓代碼更容易閱讀,還有一個好處是當你的函數嵌套太深的
時候可以給你警告。留心這個警告。

在switch語句中消除多級縮排的首選的方式是讓“switch”和從屬於它的“case”標籤對齊於同
一列,而不要“兩次縮排”“case”標籤。比如:

switch (suffix) {
case 'G':
case 'g':
mem y) {
...
} else {
....
}

理由:K&R。

也請注意這種大括弧的放置方式也能使空(或者差不多空的)行的數量最小化,同時不失可
讀性。因此,由於你的螢幕上的新行是不可再生資源(想想25行的終端螢幕),你將會有更
多的空行來放置注釋。

當只有一個單獨的語句的時候,不用加不必要的大括弧。

if (condition)
action();

這點不適用於本身為某個條件陳述式的一個分支的單獨語句。這時需要在兩個分支裡都使用大
括弧。

if (condition) {
do_this();
do_that();
} else {
otherwise();
}

3.1:空格

Linux核心的空格使用方式(主要)取決於它是用於函數還是關鍵字。(大多數)關鍵字後
要加一個空格。值得注意的例外是sizeof、typeof、alignof和__attribute__,這些關鍵字
某些程度上看起來更像函數(它們在Linux裡也常常伴隨小括弧而使用,儘管在C語言裡這樣
的小括弧不是必需的,就像“struct fileinfo info”聲明過後的“sizeof info”)。

所以在這些關鍵字之後放一個空格:
if, switch, case, for, do, while
但是不要在sizeof、typeof、alignof或者__attribute__這些關鍵字之後放空格。例如,
s = sizeof(struct file);

不要在小括弧裡的運算式兩側加空格。這是一個反例:

s = sizeof( struct file );

當聲明指標類型或者返回指標類型的函數時,“*”的首選使用方式是使之靠近變數名或者函
數名,而不是靠近類型名。例子:

char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);

在大多數二元和三元操作符兩側使用一個空格,例如下面所有這些操作符:

= + - * / % | & ^ = == != ? :

但是一元操作符後不要加空格:
& * + - ~ ! sizeof typeof alignof __attribute__ defined

尾碼自加和自減一元操作符前不加空格:
++ --

首碼自加和自減一元操作符後不加空格:
++ --

“.”和“->”結構體成員操作符前後不加空格。

不要在行尾留空白。有些可以自動縮排的編輯器會在新行的行首加入適量的空白,然後你
就可以直接在那一行輸入代碼。不過假如你最後沒有在那一行輸入代碼,有些編輯器就不
會移除已經加入的空白,就像你故意留下一個只有空白的行。包含行尾空白的行就這樣產
生了。

當git發現補丁包含了行尾空白的時候會警告你,並且可以應你的要求去掉行尾空白;不過
如果你是正在打一系列補丁,這樣做會導致後面的補丁失敗,因為你改變了補丁的上下文。

第四章:命名

C是一個簡樸的語言,你的命名也應該這樣。和Modula-2和Pascal程式員不同,C程式員不使
用類似ThisVariableIsATemporaryCounter這樣華麗的名字。C程式員會稱那個變數為“tmp”
,這樣寫起來會更容易,而且至少不會令其難於理解。

不過,雖然混用大小寫名字是不提倡使用的,但是全域變數還是需要一個具描述性的名字
。稱一個全域函數為“foo”是一個難以饒恕的錯誤。

全域變數(只有當你真正需要它們的時候再用它)需要有一個具描述性的名字,就像全域函
數。如果你有一個可以計算活動使用者數量的函數,你應該叫它“count_active_users()”或者
類似的名字,你不應該叫它“cntuser()”。

在函數名中包含函數類型(所謂的匈牙利命名法)是腦子出了問題——編譯器知道那些類型而
且能夠檢查那些類型,這樣做只能把程式員弄糊塗了。難怪微軟總是製造出有問題的程式。

本地變數名應該簡短,而且能夠表達相關的含義。如果你有一些隨機的整數型的迴圈計數器
,它應該被稱為“i”。叫它“loop_counter”並無益處,如果它沒有被誤解的可能的話。類似
的,“tmp”可以用來稱呼任意類型的臨時變數。

如果你怕混淆了你的本地變數名,你就遇到另一個問題了,叫做函數增長荷爾蒙失衡綜合症
。請看第六章(函數)。

第五章:Typedef

不要使用類似“vps_t”之類的東西。

對結構體和指標使用typedef是一個錯誤。當你在代碼裡看到:

vps_t a;

這代表什麼意思呢?

相反,如果是這樣

struct virtual_container *a;

你就知道“a”是什麼了。

很多人認為typedef“能提高可讀性”。實際不是這樣的。它們只在下列情況下有用:

(a) 完全不透明的對象(這種情況下要主動使用typedef來隱藏這個對象實際上是什麼)。

例如:“pte_t”等不透明對象,你只能用合適的訪問函數來訪問它們。

注意!不透明性和“訪問函數”本身是不好的。我們使用pte_t等類型的原因在於真的是
完全沒有任何共用的可訪問資訊。

(b) 清楚的整數類型,如此,這層抽象就可以協助消除到底是“int”還是“long”的混淆。

u8/u16/u32是完全沒有問題的typedef,不過它們更符合類別(d)而不是這裡。

再次注意!要這樣做,必須事出有因。如果某個變數是“unsigned long“,那麼沒有必要

typedef unsigned long myflags_t;

不過如果有一個明確的原因,比如它在某種情況下可能會是一個“unsigned int”而在
其他情況下可能為“unsigned long”,那麼就不要猶豫,請務必使用typedef。

(c) 當你使用sparse按字面的建立一個新類型來做類型檢查的時候。

(d) 和標準C99類型相同的類型,在某些例外的情況下。

雖然讓眼睛和腦筋來適應新的標準類型比如“uint32_t”不需要花很多時間,可是有些
人仍然拒絕使用它們。

因此,Linux特有的等同於標準類型的“u8/u16/u32/u64”類型和它們的有符號類型是被
允許的——儘管在你自己的新代碼中,它們不是強制要求要使用的。

當編輯已經使用了某個類型集的已有代碼時,你應該遵循那些代碼中已經做出的選擇。

(e) 可以在使用者空間安全使用的類型。

在某些使用者空間可見的結構體裡,我們不能要求C99類型而且不能用上面提到的“u32”
類型。因此,我們在與使用者空間共用的所有結構體中使用__u32和類似的類型。

可能還有其他的情況,不過基本的規則是永遠不要使用typedef,除非你可以明確的應用上
述某個規則中的一個。

總的來說,如果一個指標或者一個結構體裡的元素可以合理的被直接存取到,那麼它們就不
應該是一個typedef。

第六章:函數

函數應該簡短而漂亮,並且只完成一件事情。函數應該可以一屏或者兩屏顯示完(我們都知
道ISO/ANSI螢幕大小是80x24),只做一件事情,而且把它做好。

一個函數的最大長度是和該函數的複雜度和縮排級數成反比的。所以,如果你有一個理論上
很簡單的只有一個很長(但是簡單)的case語句的函數,而且你需要在每個case裡做很多很
小的事情,這樣的函數儘管很長,但也是可以的。

不過,如果你有一個複雜的函數,而且你懷疑一個天分不是很高的高中一年級學生可能甚至
搞不清楚這個函數的目的,你應該嚴格的遵守前面提到的長度限制。使用輔助函數,並為之
取個具描述性的名字(如果你覺得它們的效能很重要的話,可以讓編譯器內聯它們,這樣的
效果往往會比你寫一個複雜函數的效果要好。)

函數的另外一個衡量標準是本地變數的數量。此數量不應超過5-10個,否則你的函數就有
問題了。重新考慮一下你的函數,把它分拆成更小的函數。人的大腦一般可以輕鬆的同時跟
蹤7個不同的事物,如果再增多的話,就會糊塗了。即便你聰穎過人,你也可能會記不清你2
個星期前做過的事情。

在源檔案裡,使用空行隔開不同的函數。如果該函數需要被匯出,它的EXPORT*宏應該緊貼
在它的結束大括弧之下。比如:

int system_is_up(void)
{
return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);

在函數原型中,包含函數名和它們的資料類型。雖然C語言裡沒有這樣的要求,在Linux裡這
是提倡的做法,因為這樣可以很簡單的給讀者提供更多的有價值的資訊。

第七章:集中的函數退出途徑

雖然被某些人聲稱已經過時,但是goto語句的等價物還是經常被編譯器所使用,具體形式是
無條件跳轉指令。

當一個函數從多個位置退出並且需要做一些通用的清理工作的時候,goto的好處就顯現出來
了。

理由是:

- 無條件陳述式容易理解和跟蹤
- 嵌套程度減小
- 可以避免由於修改時忘記更新某個單獨的退出點而導致的錯誤
- 減輕了編譯器的工作,無需刪除冗餘代碼;)

int fun(int a)
{
int result = 0;
char *buffer = kmalloc(SIZE);

if (buffer == NULL)
return -ENOMEM;

if (condition1) {
while (loop1) {
...
}
result = 1;
goto out;
}
...
out:
kfree(buffer);
return result;
}

第八章:注釋

注釋是好的,不過有過度注釋的危險。永遠不要在注釋裡解釋你的代碼是如何運作的:更好
的做法是讓別人一看你的代碼就可以明白,解釋寫的很差的代碼是浪費時間。

一般的,你想要你的注釋告訴別人你的代碼做了什麼,而不是怎麼做的。也請你不要把注釋
放在一個函數體內部:如果函數複雜到你需要獨立的注釋其中的一部分,你很可能需要回到
第六章看一看。你可以做一些小注釋來註明或警告某些很聰明(或者槽糕)的做法,但不要
加太多。你應該做的,是把注釋放在函數的頭部,告訴人們它做了什麼,也可以加上它做這
些事情的原因。

當注釋核心API函數時,請使用kernel-doc格式。請看
Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc以獲得詳細資料。

Linux的注釋風格是C89“/* ... */”風格。不要使用C99風格“// ...”注釋。

長(多行)的首選注釋風格是:

/*
* This is the preferred style for multi-line
* comments in the Linux kernel source code.
* Please use it consistently.
*
* Description: A column of asterisks on the left side,
* with beginning and ending almost-blank lines.
*/

注釋資料也是很重要的,不管是基本類型還是衍生類型。為了方便實現這一點,每一行應只
聲明一個資料(不要使用逗號來一次聲明多個資料)。這樣你就有空間來為每個資料寫一段
小注釋來解釋它們的用途了。

第九章:你已經把事情弄糟了

這沒什麼,我們都是這樣。可能你的使用了很長時間Unix的朋友已經告訴你“GNU emacs”能
自動幫你格式化C原始碼,而且你也注意到了,確實是這樣,不過它所使用的預設值和我們
想要的相去甚遠(實際上,甚至比隨機打的還要差——無數個猴子在GNU emacs裡打字永遠不
會創造出一個好程式)(譯註:請參考Infinite Monkey Theorem)

所以你要麼放棄GNU emacs,要麼改變它讓它使用更合理的設定。要採用後一個方案,你可
以把下面這段粘貼到你的.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 tab-width 8)
(setq indent-tabs-mode t)
(setq c-basic-offset 8))

這樣就定義了M-x linux-c-mode命令。當你hack一個模組的時候,如果你把字串
-*- linux-c -*-放在頭兩行的某個位置,這個模式將會被自動調用。如果你希望在你修改
/usr/src/linux裡的檔案時魔術般自動開啟linux-c-mode的話,你也可能需要添加

(setq auto-mode-alist (cons '("/usr/src/linux.*/.*//.[ch]$" . linux-c-mode)
auto-mode-alist))

到你的.emacs檔案裡。

不過就算你嘗試讓emacs正確的格式化代碼失敗了,也並不意味著你失去了一切:還可以用“
indent”。

不過,GNU indent也有和GNU emacs一樣有問題的設定,所以你需要給它一些命令選項。不
過,這還不算太糟糕,因為就算是GNU indent的作者也認同K&R的權威性(GNU的人並不是壞
人,他們只是在這個問題上被嚴重的誤導了),所以你只要給indent指定選項“-kr -i8”
(代表“K&R,8個字元縮排”),或者使用“scripts/Lindent”,這樣就可以以最時髦的方式
縮排原始碼。

“indent”有很多選項,特別是重新格式化注釋的時候,你可能需要看一下它的手冊頁。不過
記住:“indent”不能修正壞的編程習慣。

第十章:Kconfig設定檔

對於遍布源碼樹的所有Kconfig*設定檔來說,它們縮排方式與C代碼相比有所不同。緊挨
在“config”定義下面的行縮排一個定位字元,協助資訊則再多縮排2個空格。比如:

config AUDIT
bool "Auditing support"
depends on NET
help
Enable auditing infrastructure that can be used with another
kernel subsystem, such as SELinux (which requires this for
logging of avc messages output). Does not do system-call
auditing without CONFIG_AUDITSYSCALL.

仍然被認為不夠穩定的功能應該被定義為依賴於“EXPERIMENTAL”:

config SLUB
depends on EXPERIMENTAL && !ARCH_USES_SLAB_PAGE_STRUCT
bool "SLUB (Unqueued Allocator)"
...

而那些危險的功能(比如某些檔案系統的寫支援)應該在它們的提示字串裡顯著的聲明這
一點:

config ADFS_FS_RW
bool "ADFS write support (DANGEROUS)"
depends on ADFS_FS
...

要查看設定檔的完整文檔,請看Documentation/kbuild/kconfig-language.txt。

第十一章:資料結構

如果一個資料結構,在建立和銷毀它的單線執行環境之外可見,那麼它必須要有一個引用計
數器。核心裡沒有垃圾收集(並且核心之外的垃圾收集慢且效率低下),這意味著你絕對需
要記錄你對這種資料結構的使用方式。

引用計數意味著你能夠避免上鎖,並且允許多個使用者並行訪問這個資料結構——而不需要擔心
這個資料結構僅僅因為暫時不被使用就消失了,那些使用者可能不過是沉睡了一陣或者做了一
些其他事情而已。

注意上鎖不能取代引用計數。上鎖是為了保持資料結構的一致性,而引用計數是一個記憶體管
理技巧。通常二者都需要,不要把兩個搞混了。

很多資料結構實際上有2級引用計數,它們通常有不同“類”的使用者。子類計數器統計子類用
戶的數量,每當子類計數器減至零時,全域計數器減一。

這種“多級引用計數”的例子可以在記憶體管理(“struct mm_struct”:mm_users和mm_count)
和檔案系統(“struct super_block”:s_count和s_active)中找到。

記住:如果另一個執行線索可以找到你的資料結構,但是這個資料結構沒有引用計數器,這
裡幾乎肯定是一個bug。

第十二章:宏,枚舉和RTL

用於定義常量的宏的名字及枚舉裡的標籤需要大寫。

#define CONSTANT 0x12345

在定義幾個相關的常量時,最好用枚舉。

宏的名字請用大寫字母,不過形如函數的宏的名字可以用小寫字母。

一般的,如果能寫成內嵌函式就不要寫成像函數的宏。

含有多個語句的宏應該被包含在一個do-while代碼塊裡:

#define macrofun(a, b, c) /
do { /
if (a == 5) /
do_this(b, c); /
} while (0)

使用宏的時候應避免的事情:

1) 影響控制流程程的宏:

#define FOO(x) /
do { /
if (blah(x) f))

還有可以做嚴格的類型檢查的min()和max()宏,如果你需要可以使用它們。你可以自己看看
那個標頭檔裡還定義了什麼你可以拿來用的東西,如果有定義的話,你就不應在你的代碼裡
自己重新定義。

第十八章:編輯器模式行和其他需要羅嗦的事情

有一些編輯器可以解釋嵌入在源檔案裡的由一些特殊標記標明的配置資訊。比如,emacs
能夠解釋被標記成這樣的行:

-*- mode: c -*-

或者這樣的:

/*
Local Variables:
compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
End:
*/

Vim能夠解釋這樣的標記:

/* vim:set sw=8 noet */

不要在原始碼中包含任何這樣的內容。每個人都有他自己的編輯器配置,你的源檔案不應
該覆蓋別人的配置。這包括有關縮排和模式配置的標記。人們可以使用他們自己定製的模
式,或者使用其他可以產生正確的縮排的巧妙方法。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.