當前位置:
首頁 > 最新 > 這是一篇描述 linux 內核首選編碼樣式的文檔

這是一篇描述 linux 內核首選編碼樣式的文檔

每個人都有自己的編碼風格,我不會將我的觀點強加到任何人的身上,但這正是我所要保持的東西,就像其他許多事情一樣。至少請考慮在這裡所列出的觀點。

首先,我建議列印出GNU編碼標準的副本,不要去閱讀,直接將它燒毀。這是一個偉大的象徵性的姿態。

好,現在正式開始:

第1章:縮進

T一個Tab鍵有8個字元位因此一個縮進也是8個字元位 . 有人試圖將一個縮進定義場4個字元位甚至2個 , 這無異於試圖將Pi的值定義為3.

說明: 縮進的意義在於定義語句塊的開始和結尾 .尤其是當你緊盯屏幕20小時後你就更加能體會到縮進的作用了 .

現在有些人說8字元長的縮進使得代碼太靠右邊,且很難在80字元的終端窗口閱讀。事實是,如果你的代碼需要三層以上的縮進,這是你犯糊塗了,你得修改你的程序。

總之,8字元縮進會讓程序更好閱讀,而而外的好處則是能夠提醒你你的程序嵌套的太深了。這個需要警惕。

精簡switch 語句中縮進界級別的最好方式就是將"switch"和其下級的"case"放在同一列,而不是雙重縮進"case"標籤。例如:

不要將多個語句放在同一行,除非你要掩飾什麼:

同樣也不要將多個賦值放在同一行。內核編程範式超簡單。避免複雜的表達式。

還有一個潛規則,是Kconfig 推薦的,永遠不要用空格縮進,上面的例子是故意的。

找一個合適的編輯器,記得不要在行末留有空格。

第二章:換行

編程範式的意義在於使用常見工具時的可閱讀和可維護性。

行的長度限制為80列,這是強烈推薦的設置。

多於80列的語句將被分為合適的數塊。子句應該永遠比主句短,且比主句更靠右側。這對有較長參數表的函數聲明同樣有效。長字元串同樣也被打斷為短字元串。這樣做能在超過80列時提高可閱讀性,且不會隱藏信息。

第三章:大括弧和空格

關於C 風格編程的另一個議點就是大括弧。跟縮進值不同,大括弧的放置並沒有技術因素在內,但「先賢「 Kernighan 和 Ritchie 展示為我們的最好方式,是將左大括弧放置在行末尾。而右大括弧放置在行首,如下:

對非函數的語句塊也是如此(if、switch、for、while、do)。例如:

當然那,有一個特殊的例外,即函數:它的左大括弧在新行的行首,如下:

全世界有不同意見的人都認為此不一致的地方——好吧——很是缺乏一致性,不過只要是能正常思考的都知道《C程序設計語言(第一版)》中是right而《C程序設計語言(第二版)》中則是 right。無論如何,函數都是特殊的(在 C 語言中你不能嵌套定義它們)。

注意,右大括弧單獨一行,除非後面跟著的是同一個語句。例如do 語句中的 "while"或 if 語句中的 "else" 如下:

闡述:K&R, 《C程序設計語言》一書的縮寫。

同時要注意,這种放置大括弧的方式能夠在不影響可讀性的同時,有效減少空行(或近乎空行)的數量。因此,你不許要刷新資源就可以看倒更多行(想想一下只能顯示25行的終端窗口),且你能夠有空多的空行來安置注釋。

只有一行語句時不用添加多餘的大括弧。

判斷語句中只有一個分支為單行語句是,不支持這樣用,你需要在所有分支中都加入大括弧。

3.1:空格

linux內核風格下的空格,其實際應用主要是取決於函數標識符的使用。大多數函數在其標識符後面會加上空格。當然,sizeof, typeof, alignof, 和 __attribute__,像這樣的長得像函數(但是因為不需要,所以經常屁股後面不跟著圓括弧。例如:"sizeof info" 如果在 "struct fileinfo info;"這個聲明之後……木有括弧……)的,就不需要加空格了。

因此你就知道,在如下關鍵字後面加個空格:

if, switch, case, for, do, while

而sizeof, typeof, alignof, or __attribute__後面就免了吧,例如

當聲明指針或者聲明一個返回指針的函數時,最好是在把個小小的「*」離著標識符(不論是變數常量還是函數)近一點,而不是和數據類型近一點,如:

在雙目或者三目運算符周圍(左邊和右邊)用上空格——他們包括(譯者註:可能不止,並未對此進行仔細檢查):

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

但是單目運算符就算了,比如這麼幾個傢伙(譯者註:同上,未作檢查):

& * + - ~ ! sizeof typeof alignof __attribute__ defined

還有幾個特殊的,比如自增自減運算符,和他們進行運算的變數標識符中間(譯者註:原文這裡是說,前後都不用加空格)不用加空格。就是這兩個傢伙:

++ --

然後,「.」和「->」這兩個運算符,加括弧是作死的行為,你不這麼想么?

別在一行的末尾留幾個空格。一些具有智能縮進功能的編輯器會在新行的開頭適當的插上空格,然後你就可以立即繼續寫你的程序。但是部分編輯器不會自動刪除你程序每一行末尾的空格(雖然你的程序在那幾個空格之前已經結束了),甚至這會產生一個完全空白的行,期間充斥著空格這種可惡的東西。

Git在這種情況下會對你進行警告,並提醒你是否由它來為你消滅這些惱人的小東西;但是這種修補總是比不上你自己進行的修補,如果你讓Git幹了太多這樣的活,可能導致你程序行的錯亂(所謂進退失據)。

四章: 命名

C 語言是粗獷的,你的命名同樣如此。與 Modula-2 和 Pascal 不同,C 程序員不使用類似 ThisVariableIsATemporaryCounter 這樣有趣的命名。C 程序員會將變數命名為類似 "tmp"的名字,這種名字比較好寫,且不難理解。

當然,雖然混合大小寫的難以讓人接受,但對於全局變數,一個描述性的名字卻是必須的。調用一個名為"foo" 全局函數顯然是在找不自在。

全局變數(只有你真正需要的時候,再用它)需要一個描述性的名字,全局函數也是。如果你有一個計算活躍用戶數量的函數,那麼命名其為"count_active_users()" 或者類似的名字,而非"cntusr()".

將函數類型添加到其名字中(即匈牙利命名法)是有害大腦的——編譯器知道、也會檢查它的,這隻能混淆程序員。所以微軟才會生產那麼多充滿BUG的程序。

局部變數的名字應當簡潔了當。如果在循環中需要一些隨機數字,你大可以命名其為"i" 。只要不會產生歧義,命名為 "loop_counter" 毫無意義。同樣的,"tmp" 可以是任何類型的臨時變數。

如果你擔心弄混局部變數的名字,那你有另一個問題:函數增長荷爾蒙失調綜合症。

第5章: Typedefs

不要傻呼呼地使用像"vps_t"這樣的變數類型.

使用typedef來重定義已有結構體和指針本身就是個錯誤. 當你在源代碼中見到這樣的定義:

vps_t a;

天知道a到底是個什麼東西!

如果你看到這樣的定義:

struct virtual_container *a;

你完全可以一目然: 哦, a是一個指向...的指針.

多數人都覺得typedefs可以提高可閱讀性, 但真理往往掌握在少數人手中. Typedefs只有在如下情況下有用:

(a) 需要被封裝起來的對象(你本來就打算隱藏起類型信息)

如: "pte_t"這種類型. 封裝出這樣的類型本來就只打算讓特定的"訪問函數"才能訪問.

請注意: 封裝以及"訪問函數"本來就不是什麼好東西. The reason we have them for things like pte_t etc. is that there

really is absolutely _zero_ portably accessible information there.

(b) 定長的整數類型. 這樣可以在避免在某些情況下, 搞不清楚到底用的是int還是long, 把你自己搞暈!

u8/u16/u32都是完美的typdefs, 儘管更應該它們歸結至規則(d)下.

再次重申: 這樣定義必須要有合理的理由. 如果某個變數本來就是unsigned long類型, 你硬要它定義成這樣,你就是SB了:

typedef unsigned long myflags_t;

如果你有明確的理由, 在某種情形下變數是unsigned int類型, 而在另外的情形下又要變身成為unsigned long類型, 那就儘管去typedef.

(c) 當你需要使用kernel的sparse工具做變數類型檢查時, 你也可以typedef一個類型.

(d)對於特殊情況下的某些c99標準的新類型

你的大腦和眼睛只需要很短的時間就可以習慣像"uint32_t"這樣的新類型,雖然有的人反對這宗用法。

因此,雖然對於你的新代碼來說,linux獨有的"u8/u16/u32/u64"類型並不是強制的,但是他們也是與標準類型等價的。

當編輯現有代碼時,如果其中已經使用了某一種類型名規範,你應該遵循原樣,使用與之相同的類型名。

(e)用戶空間中的類型安全

對於某些結構,顯然我們不能使用c99標準的類型,不能使用上述的『u32』,因此咱乾脆在結構中使用"_u32"或者類似的類型好了。

也許還有其它情況,但基本規則是,除非你很清除符合某一條規則,否則永遠不要使用typedef。

通常,一個指針或是一個含有元素的結構體,若能直接訪問,永遠不是typedef。

第6章:函數

函數這種東西,應該小而精,換句話說,只是專心的做一件事情一直到底。如果你把它赤身裸體的展示於屏幕之上,你應該在兩個屏幕(這裡一個屏幕的大小根據ISO/ANSI標準應該是80X24的)之內就可以看完它。

函數的長度和縮進程度和它的複雜程度是成反比的。所以,如果你的函數確實單純(譯者註:是萌妹子那樣的單純不?)而簡單,比如用一個長長的switch-case語句來做點簡簡單單的事情來處理一些單單純純的情況,來吧,咱把函數寫再長一點。

相反的,如果你的函數相當的複雜,複雜到你嚴重懷疑一個普普通通的不想你一樣天才的高中學生看不懂……返工吧,把函數縮短縮短再縮短,不要猶豫,多造幾個輔助函數並給他們幾個一看即知的名字,以減少函數複雜性。(當然,如果你覺得這個函數太關鍵了,如果拆開了寫肯定會影響整個程序的性能,那你乾脆內聯,用那個內聯函數或者使用編譯器的內聯功能)

規範對於函數的另外一個要求是關於局部變數的個數的:最多5-10個。就算你是天才,你的腦子一般也就能輕鬆關注7個變數,再多了就絕對會出現一些你意想不到的錯誤(誰叫你一心多用來著)。好吧,就算你是天才,如果還想明白你在兩周之前所寫的那些程序的話,還是遵守這些規範吧。

就源代碼來說,函數與函數的原型之間應該有一個空白行作為分隔。如果該函數在其他文件中被引用,那麼他的EXPORT*宏應當和函數最後一行的那個大括弧中間沒有任何空格。例如:

另外,在函數原型中,當聲明參量時應該將參量的類型和標識符放到一起,雖然c語言本身並不要求這種形式,但是在linux kernal裡面是要求的——目的是增加每一行代碼的信息量。

章7:集中於一處退出函數

雖然很多人不提倡,但是我們這裡要經常使用goto進行無條件的跳轉。

當函數有很多個出口,使用goto把這些出口集中到一處是很方便的,特別是函數中有許多重複的清理工作的時候。

理由是:

-無條件跳轉易於理解

-可以減少嵌套

-可以避免那種忘記更新某一個出口點的問題

-算是幫助編譯器做了代碼優化

第8章:注釋

有注釋當然是好的,但是注釋太多就很噁心了。千萬不要在注釋裡面解釋你的程序怎麼運行的。相對於嘗試用注釋解釋清楚你那噁心的代碼,你還不如就寫個清晰易懂(譯者註:就是小而精,萌妹子一樣單純的~)的函數。

一般的,你的注釋是用來說明這段代碼是「幹啥的」,而不是「怎麼乾的」。另外,別把注釋放到你的函數體裡面(譯者註:把我放到妹子的懷抱里吧!):如過你的函數確實複雜到需要用注釋來分隔成幾段,回第六章擦亮眼睛再看兩遍(譯者註:那一段正好也是我翻譯的)。以可以用幾個小注釋來提醒大家一些東西(「寫的好~」或者「寫的真tm糟糕」),但是就不要自取其辱評論自己的東西了……最後,仍然提醒你,一定是把注釋安放在函數的前面,然後簡單寫下這段函數式幹啥的,如果可能,你倒是不妨提及為什麼你要這樣做。

當給kernal api函數注釋的時候,請使用kernal-doc格式。該格式的細節你可以參考這兩個文件:Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc(譯者註:這個文件路徑應當是指內核源碼中的路徑)

linux當中的注釋是c89格式("/* ... */")的,而不是c99中新近添加的"// ..."

多於一行(多行)的注釋應當準從以下格式:

該格式對於注釋標識符(常量,變數,函數等)同樣適用。換句話說,你最好不要再一行裡面同時聲明很多個標識符(無論是用逗號還是分號隔開都是不推薦的),一行一個就可以了。這樣你就可以在每一行對每一個標識符進行解釋。

第九章:內存分配

內核提供了下列通用內存分配器:kmalloc()、kzalloc()、kcalloc()、vmalloc()、和 vzalloc()。 更多信息,請參閱的 API 文檔。

傳遞一個結構體大小的最好方式如下:

另外一種傳遞方式中,sizeof的操作數是結構體的名字,這樣有損可讀性,並會在指針類型改變,但傳遞給內存分配器的大小沒有變化時導致BUG。

強制轉換返回的void指針是冗餘的。C語言本身保證了從void指針到其他任何指針類型的轉換。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嵌入式ARM 的精彩文章:

嵌入式Linux驅動開發基礎總結

TAG:嵌入式ARM |