C 語言代碼風格之 Linux 內核代碼風格
GitHub: https://github.com/storagezhang
Emai: debugzhang@163.com
本文翻譯自 https://www.kernel.org/doc/html/v4.10/_sources/process/coding-style.txt
本文簡短描述了 Linux 內核推薦的代碼風格。代碼風格是非常個性化的,但這是 Linux 內核必須維持的準則,對于很多其他領域的代碼,該規范也具有參考意義。
首先,請打印出 GNU 代碼規范,不要閱讀,燒掉它們,這是一個很棒的象征性手勢。
1 縮進
Tab 占 8 個字符,因此縮進也是 8 個字符。有一些異端運動試圖減少縮進至 4 個字符(甚至 2 個字符),類似的嘗試是將 PI 的值定義為 3。
原因:縮進的整體思想是明確定義控制塊的開始和結束位置。特別是當你連續看屏幕 20 個小時時,你會發現當縮進值較大時,注意到縮進會更加輕松。
現在,有些人表示 8 個字符的縮進使代碼向右移得太遠,難以在寬度為 80 字符的終端屏幕上閱讀。
這個問題的答案是:如果你需要三個以上的縮進級別,無論如何程序都會被搞砸,應當修改程序。
簡而言之,8 字符縮進使代碼更易于閱讀,并且在函數嵌套過深時發出警告。注意這個警告。
減少 switch 語句中的多級縮進的首選方法是將 switch 與 case 標簽在同一列對齊而不是縮進 case 標簽:
switch (suffix) { case 'G': case 'g': mem <<= 30; break; case 'M': case 'm': mem <<= 20; break; case 'K': case 'k': mem <<= 10; /* fall through */ default: break; }
除非要隱藏某些內容,否則不要將多個語句放在同一行上:
if (condition) do_this; do_something_everytime;
也不要將多個任務放在同一行上。內核代碼風格非常簡單。避免使用復雜的表達式。
除了在注釋、文檔和 Kconfig 之外,空格都不能用于縮進,上述示例被故意破壞了。
得到一個體面的編輯,不要在行尾留空白。
2 打破長行和長字符串
代碼風格作用于常用工具,使工具增加可讀性和可維護性。
行的長度限制為 80 列,這是一個強優先限值。
除非該行超過 80 列會顯著提高可讀性并且不會隱藏信息,長度超過 80 列的語句將被分成合理的塊。后代始終比父代短很多,并且基本上位于右側。具有長參數列表的函數頭也是如此。但是切勿破壞諸如 printk 消息之類的用戶可見的字符串,因為這會破壞為它們進行 grep 的能力。
3 放置大括號和空格
C 風格中經常出現的另一個問題是大括號的位置。
與縮進尺寸不同,沒有什么技術上的原因讓我們選擇一種放置策略而不是另一種,但是正如
K
e
r
n
i
g
h
a
n
Kernighan
Kernighan?和
R
i
t
c
h
i
e
Ritchie
Ritchie?向我們展示的一樣,首選方式是將開括號放在一行的最后,將閉括號放在新行:
if (x is true) { we do y }
這實用與所有非函數語句塊(if, switch, for, while, do)。例如:
switch (action) { case KOBJ_ADD: return "add"; case KOBJ_REMOVE: return "remove"; case KOBJ_CHANGE: return "change"; default: return NULL; }
但是有一個例外,即函數:函數的開括號在下一行的開頭:
int function(int x) { body of function }
全世界的異端人士都聲稱這種矛盾是矛盾的,但是所有有正確思想的人們都知道
K
&
R
K\&R
K&R 是正確的。
此外,函數還是很特殊的(你不能將它們嵌套在 C 代碼里)。
請注意,閉括號所在的行是空的,除非在其后接同一語句的延續,即 while?在 do?語句后或 else 在 if?語句后,像這樣:
do { body of do-loop } while (condition);
和這樣:
if (x == y) { .. } else if (x > y) { ... } else { .... }
理由:
K
&
R
K\&R
K&R。
另外請注意:這種括號放置策略還可以在不損害可讀性的前提下,最大程度地減少空(或幾乎空)行的數量。
因此,由于屏幕上的新行供應是不可再生資源(請在此處考慮 25 行高的終端屏幕),你講有更多的空行可以用來編寫注釋。
不要在使用單個語句的地方不必要地使用大括號。
if (condition) action();
和:
if (condition) do_this(); else do_that();
如果多個條件語句中只有一個分支是單個語句,則不適用該規則。在這種情況下,需要在所有的分支中都使用大括號:
if (condition) { do_this(); do_that(); } else { otherwise(); }
3.1 空格
Linux 內核風格使用空格的樣式(主要)取決于函數與關鍵字的用法。
在大多數關鍵字之后使用一個空格。值得注意的例外是 sizeof, typeof, alignof, __attribute__,看起來有點像函數(通常在 Linux?中使用時需要加括號,雖然它們并沒有要求,如在 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
后綴遞增和遞減一元運算符前沒有空格:
++ --
前綴遞增和遞減一元運算符后沒有空格:
++ --
使用 . 和 -> 運算符也不需要空格。
不要在行尾留下尾隨空格。某些帶有智能縮進的編輯器會在適當的時候在新行的開頭插入空格,從而讓你可以立即開始輸入下一行代碼。但是,如果最終沒有在新行放置代碼(留空行),某些編輯器不會刪除空格,最終導致包含尾隨空格的行。
G
i
t
Git
Git 會警告你有關引入尾隨空格的補丁,并且可以有選擇地為你剝離尾隨空格。但是如果應用一系列補丁,可能會由于上下文的更改導致該系列的后續補丁失敗。
4 命名
C 是一門簡潔的語言,所以命名也應該如此。與 Modula-2 和 Pascal 程序員不同,C 程序員不會使用諸如ThisVariableIsATemporaryCounter?之類的可愛命名。一個 C 程序員將命名該變量為 tmp,這樣更容易編寫,但同時也更難理解。
雖然不贊成使用大小寫混合的命名方式,但描述性名稱命名全局函數是必須的。
全局變量(僅在確實需要它們時才使用)與全局函數一樣,都需要使用具有描述性的名稱命名。如果現在有一個函數用來統計活躍用戶數,應該將它命名為 count_active_users() 或類似的,而不應該命名為 cntusr()。
將函數的類型編碼為名稱(所謂的匈牙利命名法)是不好的——編譯器無論如何都知道它們的類型并且可以檢查它們,這樣只會使程序員感到困惑。難怪
M
i
c
r
o
S
o
f
t
MicroSoft
MicroSoft 寫出了很多古怪的程序。
局部變量名和應該簡單明了,切中要害。如果你有一些隨機的整數循環變量,可以將它命名為 i。如果該程序沒有可能被誤解,命名為 loop_counter 是沒有必要的。同樣,tmp?幾乎可以用于任何類型的保存臨時值的變量。
如果你害怕混淆你的局部變量名,你會遇到另一個問題,它通常被稱為“生長激素功能失調綜合征”。參見第六章(函數)。
5 Typedefs
請不要使用諸如 vps_t?之類的類型別名。對結構體和指針使用此類別名是一種錯誤。當你在源碼中看到
vps_t a;
這是什么意思?
相反,如果是這樣
struct virtual_container *a;
你就能知道 a?實際上到底是什么。
許多人認為 typedefs?提高了可讀性。但并不是這樣,它們僅對以下內容有用:
完全不透明的對象(typedef 用于隱藏對象的內容)。
示例:pte_t 等不透明的對象,你只能通過合適的訪問函數進行訪問。
注意:不透明性和訪問函數并不是一種好的方法。之所以將它們用于類似 pte_t?的對象,是因為它們確實存在不可訪問的信息。
明確整數類型,這種抽象有助于避免混淆 int 和 long。u8/u16/u32 是完美的 typedefs。
注意:
如果如果有任何量是 unsigned long,那就沒有理由使用 typedef unsigned long myflags_t。
但是如果有明確的理由說明為什么在某些情況下它可能是 unsigned long,而在另一些配置下可能是 unsigned long,那就繼續使用 typedef。
當你使用 sparse?創建一個用于類型檢查的新類型。
在某些特殊情況下,與 C99 標準類型相同的新類型。
盡管只需要很短的時間就可以使眼睛和大腦適應像這樣的標準類型 uint32_t,但是無論如何,有些人還是反對使用它們。
因此,
L
i
n
u
x
Linux
Linux 特定類型 u8/u16/u32/u64 及其有符號類型被允許定義別名,盡管它們在新代碼中不是必須的。
當編輯已使用一種或另一組類型的現有代碼時,應當遵循該代碼中的現有選擇。
在用戶空間保持類型安全。
在用戶空間可見的某些結構體中,我們不能使用 C99 標準類型,也不能使用類似 u32?的形式。因此,我們應當在所有與用戶空間共享的結構的體中使用 __u32?和類似類型。
也許還有其他情況,但是一個基本規則是:除非你可以清楚地匹配上述規則之一,否則不要使用 typedef。
通常,指針或者具有可以合理地直接訪問元素的結構體永遠都不應該使用 typedef。
6 函數
函數應該簡潔而優美,每個函數只做一件事。它們應該適合一到兩個文本(
I
S
O
/
A
N
S
I
ISO/ANSI
ISO/ANSI 屏幕的尺寸是 80
×
\times
× 24)。
函數的最大長度與該函數的復雜度和縮進程度成反比。因此,如果你有一個概念上很簡單的函數,但是有一個長而是簡單的 case 語句,通過這些語句完成很多不同的小任務,那么讓該函數更長是可行的。
但是,如果你有一個復雜的函數,并且懷疑一般人無法理解該函數的全部含義,那么你應該更加嚴格地遵守最大長度限制,并使用具有描述性名稱的輔助函數(如果你認為該輔助函數對性能至關重要,可以將其設置為內聯函數)。
函數的另一個度量是局部變量的數量,它們不應該超過 5-10 個,否則就是你的函數有什么問題。重新規劃該函數,考慮將其拆分為較小的部分。人腦通常可以輕松地跟蹤大約 7 種不同的事物,再多就會變得混亂。你知道自己很聰明,但是也許你想輕松理解 2 周前做的工作。
在源文件中,用一個空行分割函數。如果需要導出該函數,則 EXPORT?宏應該緊跟在函數結束的括號行之后。例如:
int system_is_up(void) { return system_state == SYSTEM_RUNNING; } EXPORT_SYMBOL(system_is_up);
函數原型應該包含參數名稱及其數據類型。盡管這不是 C 語言所必需的,但我們建議在
L
i
n
u
x
Linux
Linux 中這樣做,因為它是一種簡單的為讀者添加有效信息的方法。
7 函數集中退出
盡管有些人不贊同使用 goto 語句,但是編譯器經常以無條件跳轉指令的形式使用與 goto 語句等效的語句。
當函數可能從多個位置退出,并且必須執行一些常規工作(例如清理)時,goto 語句就會派上用場。如果不需要清理,則直接返回。
標簽命名需要說明 goto 的功能或 goto 存在的原因。當一個 goto 是用來釋放 buffer 的空間,一個好的標簽命名是 out_free_buffer:。避免使用像 err1: 和 err2: 這樣的 GW-BASIC 命名,因為當你添加或刪除退出路徑時,你需要重新命名它們,并且它們會使正確性驗證變得非常困難。
使用 goto 的邏輯依據是:
無條件語句更容易理解和遵循;
減少嵌套;
防止在進行修改時沒更新退出點而導致錯誤;
節省編譯器優化冗余代碼的工作。
int fun(int a) { int result = 0; char *buffer; buffer = kmalloc(SIZE, GFP_KERNEL); if (!buffer) return -ENOMEM; if (condition1) { while (loop1) { ... } result = 1; goto out_free_buffer; } ... out_free_buffer: kfree(buffer); return result; }
注意常見的錯誤類型,例子如下:
err: kfree(foo->bar); kfree(foo); return ret;
該錯誤是在某些退出路徑上 foo?為 NULL。此問題的解決方法是將其分為兩個錯誤標簽 err_free_bar:?和 err_free_foo::
err_free_bar: kfree(foo->bar); err_free_foo: kfree(foo); return ret;
理想情況下,你應該模擬錯誤以測試所有的退出路徑。
8 注釋
注釋是一個好習慣,但是過度注釋也帶來一些壞處。永遠不要嘗試在注釋中解釋代碼的工作方式——編寫高質量的代碼從而讓工作方式顯而易見是一種更好的選擇,而不是浪費時間來解釋低質量的代碼。
通常,你的注釋應該用來解釋代碼的功能,而不是解釋代碼如何實現這項功能。此外,盡量避免在函數體內添加注釋——如果函數太過復雜以至于需要單獨注釋其中的某些部分,請重新閱讀第 6 部分。你可以添加一些短注釋以提醒或警告某些特別精巧(或丑陋)的地方,但請盡量避免。最好是將這些注釋放在函數的開頭,解釋它做了什么,可能還包括它為什么這么做。
在注釋內核 API 函數時,請使用
k
e
r
n
e
l
?
d
o
c
kernel-doc
kernel?doc?格式。你可以在這里(Documentation/doc-guide/)看到更多細節。
多行注釋推薦的風格如下:
/* * 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. */
對于在 net/?和 drivers/net/?中的文件,多行注釋的風格有一點不同:
/* The preferred comment style for files in net/ and drivers/net * looks like this. * * It is nearly the same as the generally preferred comment style, * but there is no initial almost-blank line. */
對數據(無論是基本類型還是派生類型)進行注釋也很重要。為此,每行僅對一個數據進行聲明(對于多個數據聲明,請不要使用逗號)。這為你留出了對每個數據進行簡短評論的空間,以說明該數據的用途。
9 你搞砸了
這不是大問題,我們都有這樣的經歷。Unix 用戶助手可能已經告訴你 GNU emacs 會自動幫你格式化 C 代碼。你可能已經注意到,GNU emacs 確實可以做到這一點,但是使用默認值的實際體驗并不好(實際上,它們比隨機輸入更糟糕)。
所以,你可以擺脫 GNU emacs,或者將其改為更合理的值。為此,你可以將下列內容粘貼到你的 .emacs?配置文件中:
(defun c-lineup-arglist-tabs-only (ignored) "Line up argument lists by tabs, not spaces" (let* ((anchor (c-langelem-pos c-syntactic-element)) (column (c-langelem-2nd-pos c-syntactic-element)) (offset (- (1+ column) anchor)) (steps (floor offset c-basic-offset))) (* (max steps 1) c-basic-offset))) (add-hook 'c-mode-common-hook (lambda () ;; Add kernel style (c-add-style "linux-tabs-only" '("linux" (c-offsets-alist (arglist-cont-nonempty c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)))))) (add-hook 'c-mode-hook (lambda () (let ((filename (buffer-file-name))) ;; Enable kernel mode for the appropriate files (when (and filename (string-match (expand-file-name "~/src/linux-trees") filename)) (setq indent-tabs-mode t) (setq show-trailing-whitespace t) (c-set-style "linux-tabs-only")))))
這將使 emacs?更好地與 ~/src/linux-trees?目錄下的內核風格的 C 文件兼容。
但是即使你無法使用 emacs 進行理想的格式化,仍然還有其他方法:使用 indent。
同樣,GNU indent 有一些與 GNU emacs 相同的失效配置,這就是為什么你需要為其提供一些命令行選項。但這還不算太糟,因為即使是 GNU indent 的開發者也意識到
K
&
R
K\&R
K&R 的權威(GNU 的開發者并不邪惡,他們只是在這類問題上受到嚴重誤導),所以你只需要給 indent 提供選項 -kr -i8(K&R, 8 character indents 標準),或使用 scripts/Lindent,它的縮進風格是最新風格。
indent 有很多選項,尤其是在對注釋重新格式化的時候,你可能需要仔細看一下 man 手冊。但請記住,indent 不是針對不良編程的解決方案。
10 Kconfig 配置文件
對于源代碼樹中的所有 Kconfig*?配置文件,它們的縮進有所不同。config?定義下的行以一個 tab?為單位縮進,而幫助文本以兩個空格縮進。例如:
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.
嚴重危險的特性(例如對某些文件系統的寫支持)應該在其提示中突出表明這一點:
config ADFS_FS_RW bool "ADFS write support (DANGEROUS)" depends on ADFS_FS ...
有關配置文件的完整文檔,參見 Documentation/kbuild/kconfig-language.txt。
11 數據結構
對于在單線程環境內創建和銷毀,并在線程之外可見的數據結構,應該使用引用計數。在內核中不存在垃圾回收(并且內核之外的垃圾回收是緩慢且低效的),這意味著你必須使用引用計數記錄所有的使用情況。
引用計數意味著你可以避免加鎖,允許多個用戶并行訪問數據結構,并且不必擔心正在使用的數據結構突然消失(僅僅因為程序 sleep?了一會兒或者做了一會兒其他的事情)。
注意:加鎖并不能代替引用計數。加鎖用于保持數據結構的一致性,而引用計數是一種內存管理技術。通常二者都是必須的,不要相互混淆。
很多數據結構都有 2 級引用計數,當存在不同等級的使用者時,自級計數器統計子級使用者的數量,當子級計數器變為 0 時將全局計數器減 1。
這種多級引用計數的例子可以在內存管理(struct mm_struct: mm_users and mm_count)中找到,也可以在文件系統(struct super_block: s_count and s_active)中找到。
記住:如果其他線程可以發現你的數據結構,并且你沒有使用引用計數,那么幾乎可以肯定你的代碼有 bug。
12 宏、枚舉和 RTL
定義常量的宏名稱和枚舉中的標簽均使用大寫字母。
#define CONSTANT 0x12345
最好使用枚舉定義多個相關常量。
可以使用大寫的宏名稱,但是宏函數要用小寫命名。
一般情況下,與宏函數相比更推薦使用內聯函數。
應該將包含多條語句的宏放在 do-while?塊中:
#define macrofun(a, b, c) \ do { \ if (a == 5) \ do_this(b, c); \ } while (0)
使用宏時應該避免下列情況:
避免定義影響控制流的宏:
#define FOO(x) \ do { \ if (blah(x) < 0) \ return -EBUGGERED; \ } while (0)
上述代碼是很糟糕的。它使用時看起來像函數調用,但是卻能讓調用它的函數退出,這會打斷讀者閱讀代碼時的思維。
避免定義依賴于固定名稱的局部變量的宏:
#define FOO(val) bar(index, val)
上述代碼看起來像是做了一件好事,但是當讀者閱讀這段代碼時會感到很困惑,并且這段代碼很容易被“看似無害的更改”破壞。
避免定義帶參并做左值的宏:FOO(x) = y
如果有人將 FOO 轉換為內斂函數,會導致程序崩潰。
避免依照優先級定義宏:
使用宏通過表達式定義常量時,需要將表達式用括號括起來。注意:帶參數的宏同樣需要考慮該問題。
#define CONSTANT 0x4000 #define CONSTEXP (CONSTANT | 3)
避免在宏函數中定義局部變量時的命名空間沖突:
#define FOO(x) \ ({ \ typeof(x) ret; \ ret = calc_ret(x); \ (ret); \ })
ret 是定義局部變量時的常用名,而 __foo_ret 則不太可能與已有的局部變量發生沖突。
cpp 手冊詳盡描述了宏,gcc 內部手冊還介紹了在內核中經常與匯編語言一起使用的 RTL。
13 打印內核消息
內核開發者喜歡被看作是有學問的人。一定要注意內核消息的拼寫,給人留下好印象。不要使用類似 dont 之類的蹩腳單詞,使用 do not 或 don't 代替它。
內核消息要簡潔、清晰并且無歧義。
內核消息不必以句號結尾。
應避免打印帶括號的數字,因為這樣毫無意義。
linux/device.h 中有許多驅動程序模型診斷宏,你應使用這些宏來確保消息與設備和驅動是匹配的,并且使用正確級別的宏:dev_err()、dev_warn()、dev_info() 等。對于與特定設備無關的消息,使用 linux/printk.h 中定義的 pr_notice()、pr_info()、pr_warn()、pr_err() 等。
一個相當大的挑戰可能是輸出友好的調試消息。這些友好的調試消息對遠程故障排除將是一個巨大的幫助。但是打印調試消息的處理和打印非調試信息的處理是不同的。其他的 pr_XXX()?函數無條件打印,但 pr_debug()?不是,因為默認編譯的情況是不包含的該消息的,除非定義了 DEBUG?或者設置了 CONFIG_DYNAMIC_DEBUG。dev_dbg()?也是如此,通常的約定是使用 VERBOSE_DEBUG?將 dev_vdbg() 的消息添加到由 DEBUG?啟用的消息中。
許多子系統中通過 Kconfig?調試選項來開啟相關 Makefile?中的 -DDEBUG。其他情況下在特定的文件中使用#debug DEBUG?定義 DEBUG?宏。當調試信息需要無條件打印,例如它已經在與調試相關的 #ifdef?中,可以使用 printk(KERN_DEBUG ...)。
14 分配內存
內核提供以下通用的內存分配函數:kmalloc(),kzalloc(),kmalloc_array(),kcalloc(),vmalloc() 和 vzalloc()。關于它們更詳細的信息,請參考 API 文檔。
推薦的傳遞結構體大小的形式如下:
p = kmalloc(sizeof(*p), ...);
使用 struct name 的形式損害可讀性,并且當指針變量的類型發生了變化,但是傳遞給內存分配函數的與之相關的 sizeof 沒進行修改時,很容易引入 bug。
對 void 指針類型的返回值進行類型轉換是多余的。將 void 指針轉換為其它任何類型的指針是由 C 語言保證的。
推薦的分配數組的形式如下:
p = kmalloc_array(n, sizeof(...), ...);
推薦的分配所有元素的初始值為 0 的數組的形式如下:
p = kcalloc(n, sizeof(...), ...);
兩種形式都需要檢查分配大小為 n * sizeof(...)?的內存是否成功,當分配不成功時會返回 NULL。
15 內聯隱患
人們似乎普遍認為 GCC 有一個神奇的“使我更快”的加速選項——inline。雖然可以適當地使用內聯(比如作為替代宏的一種方法,參閱第 12 章),但通常情況下不是這樣的。大量使用 inline?關鍵字會導致一個更大的內核,這會反過來會減慢整個系統的速度,因為這樣會導致 CPU icache?的占用量會更大,并且用于頁面緩存的內存會更少。頁面緩存沒命中會導致磁盤查找,這很容易就會花費掉 5 毫秒,然而有很多 CPU 周期本該可以進入這 5 毫秒。
一個合理的經驗法則是不要對包含多于三行代碼的函數進行內聯。該規則的例外是:其中一個參數是編譯時確定的常量,并且由于這個常量你知道編譯器將能夠在編譯時優化函數的大部分內容。這種例外情況的好示例是 kmalloc() 內聯函數。
人們經常爭辯說給靜態的并且只使用一次的函數增加內聯是一個勝利,因為不用在空間上做權衡。盡管從技術上講這是正確的,但實際上 GCC 有能力在沒有幫助的情況下自動對它們進行內聯。并且當第二個使用者出現時需要我們手動刪除內聯內容所導致的維護問題,超過了告訴 GCC 去做一些它已經做了的事情的潛在價值。
16 函數返回值和函數名稱
函數可以返回許多不同種類的值,其中最常見的是返回表明函數執行是成功還是失敗的值。表明成功或失敗,可以使用整型值(負數代表失敗,0 代表成功),或布爾值(0 代表失敗,非 0 代表成功)。
將二者混合使用會給 bug 的排查帶來麻煩。如果 C 語言能很好的區分 integers 和 booleans,那么編譯器將會幫助我們發現這些錯誤,但事實并非如此。為防止此類 bug,請始終遵守以下約定:
如果函數名是一個動作或命令,函數應該返回 integer 類型的錯誤碼;
如果函數名是謂語,函數應該返回 boolean 類型的錯誤碼。
例如:
add work 是一個命令,函數 add_work() 返回 0 代表成功,返回 -EBUSY 代表失敗。
PCI device present 是一個謂語,pci_dev_present() 函數在發現匹配的設備時返回 1 代表成功,返回 0 代表沒發現匹配的設備。
所有的導出函數和公有函數必須遵守此約定。私有(靜態)函數不需要嚴格遵守,但也建議這么做。
函數返回實際計算的結果(而不是表明函數是否計算成功)的函數不受此規則約束。通常它們通過返回一些超出范圍的結果來表明失敗。典型的例子是返回指針的函數,他們用 NULL?或 ERR_PTR?來報告失敗。
17 不要重新實現內核已有的宏
內核頭文件 include/linux/kernel.h?包含了許多可以直接使用的宏,請直接使用它們,不要重新發明輪子。例如,你可以用下列宏計算數組的長度:
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
類似的,你可以用下列宏計算結構體中某個成員的大小:
#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
如果你需要,還有進行嚴格類型檢查的宏 min()?和 max()。請仔細閱讀這個頭文件,看看它提供了哪些你可以在代碼中直接使用的宏,而不是在代碼中重新實現。
18 編輯器和其他內容
某些編輯器可以解析嵌入在源文件中的、用特殊標記表示的配置信息。例如:emacs?可以解析源文件中按如下方式書寫的配置信息:
-*- mode: c -*-
或者按如下方式:
/* Local Variables: compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c" End: */
Vim 可以解析按如下方式書寫的配置信息:
/* vim:set sw=8 noet */
不要在源文件中包含這些信息。每個人都有自己的編輯器配置信息,你的源文件配置信息不應該覆蓋它們,這包括用于縮進和模式配置的標記。每個人都可以使用他們的自定義模式,或者有其他的使縮進正確工作的神奇方法。
19 內斂匯編
在特定體系結構的代碼中,你可能需要使用內聯匯編與 CPU 或平臺函數進行交互。當需要這么做時不用猶豫,但是能用 C 語言完成的工作請不要無緣無故地使用內聯匯編。如果可能的話,你可以也應該使用 C 語言控制硬件。
考慮編寫簡單的、包裹內聯匯編通用位的輔助函數,而不是重復編寫只有輕微變化的函數。記住,內聯匯編可以使用 C 語言的參數。
大型的、具有一定復雜度的匯編函數應該放在 .S?文件中,并且在 C 語言頭文件中定義相關的 C 語言函數原型。匯編函數的 C 語言函數原型需要使用 armlinkage。
你可能需要將匯編語句標記為 volatile,以防止防止 GCC 把他們優化掉,但不一定總是要這么做,而且多余的做法可能會限制優化。
當編寫包含多條指令的單個內聯匯編語句時,將每條指令單獨放在帶引號的字符串中并獨占一行,并且除了最后一個字符串以外,所有字符串都以 \n\t?結尾,這樣做才能使以匯編形式輸出時下一條指令能正確地縮進。
asm ("magic %reg1, #42\n\t" "more_magic %reg2, %reg3" : /* outputs */ : /* inputs */ : /* clobbers */);
20 條件編譯
盡量不要在 .c 文件中使用預處理條件(#if, #ifdef),因為這樣做會使代碼難以閱讀,更難理解代碼的邏輯。相反,在頭文件中使用預處理條件來定義那些在 .c 文件中使用的函數,在 #else 中提供 no-op stub 的版本,然后在 .c 文件中無條件地調用這些函數。編譯器將不會為調用 stub 生成任何代碼,從而產生相同的效果,并且邏輯更容易理解。
最好編譯出整個函數,而不是部分函數或部分表達式。與其將 ifdef?放在表達式中,不如將表達式的一部分或全部分解到一個單獨的輔助函數中,然后將預處理條件應用于該函數。
如果某個函數或變量在特定的配置中可能不需要被使用,編譯器會針對定義但沒使用的變量發出警告,將該定義標記為 __maybe_unused 而不是用預處理條件包裹它(如果函數或變量總是不使用則應該刪除它)。
在代碼中盡量使用 IS_ENABLE?宏將 Kconfig?中的符號轉換為 C 語言的布爾表達式,并且在普通的 C 語言條件語句中使用它:
if (IS_ENABLED(CONFIG_SOMETHING)) { ... }
編譯器將不斷地折疊這些條件,并像 #ifdef 一樣包含或排除代碼塊,所以這不會增加任何運行時的開銷。然而這種方法仍然允許 C 編譯器查看其中的代碼并檢查其正確性(語法、類型、符號引用等)。因此當條件不滿足是,塊中代碼引用的符號也不存在,這種情況下,你仍然需要使用 #ifdef。
在任何具有一定復雜度的 #if?或 #ifdef?塊的末尾 #endif?所在行的后面放置注釋來標注使用的條件表達式,例如:
#ifdef CONFIG_SOMETHING ... #endif /* CONFIG_SOMETHING */
附錄 參考
The C Programming Language, Second Edition by Brian W. Kernighan and Dennis M. Ritchie. Prentice Hall, Inc., 1988. ISBN 0-13-110362-8 (paperback), 0-13-110370-9 (hardback).
The Practice of Programming by Brian W. Kernighan and Rob Pike. Addison-Wesley, Inc., 1999. ISBN 0-201-61586-X.
GNU manuals - where in compliance with K&R and this text - for cpp, gcc, gcc internals and indent, all available from http://www.gnu.org/manual/
WG14 is the international standardization working group for the programming language C, URL: http://www.open-std.org/JTC1/SC22/WG14/
Kernel process/coding-style.rst, by greg@kroah.com at OLS 2002: http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/
C 語言 Linux
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。