<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          精心整理的萬(wàn)字Linux內(nèi)核源碼規(guī)范

          共 3621字,需瀏覽 8分鐘

           ·

          2020-12-21 16:07

          ID:小麥大叔

          作者:菜刀和小麥


          • 從編碼風(fēng)格錯(cuò)誤開(kāi)始

          • 快速修改編碼風(fēng)格的工具

            • scripts/checkpatch.pl

            • scripts/Lindent

            • astyle

          • Linux 內(nèi)核代碼風(fēng)格

          • 1 縮進(jìn)

          • 2 把長(zhǎng)的行和字符串打散

          • 3 大括號(hào)和空格的放置

          • 4 命名

          • 5 Typedef

          • 6 函數(shù)

          • 7 集中的函數(shù)退出途徑

          • 8 注釋

          • 9 你已經(jīng)把事情弄糟了

          • 10 Kconfig 配置文件

          • 11 數(shù)據(jù)結(jié)構(gòu)

          • 12 宏,枚舉和RTL

          • 13 打印內(nèi)核消息

          • 14 分配內(nèi)存

          • 15 內(nèi)聯(lián)弊病

          • 16 函數(shù)返回值及命名

          • 17 不要重新發(fā)明內(nèi)核宏

          • 18 編輯器模式行和其他需要羅嗦的事情

          • 19 內(nèi)聯(lián)匯編

          • 20 條件編譯


          從編碼風(fēng)格錯(cuò)誤開(kāi)始

          曾經(jīng)在開(kāi)發(fā)Linux內(nèi)核驅(qū)動(dòng)的時(shí)候,創(chuàng)建了一個(gè)補(bǔ)丁文件,但是在把補(bǔ)丁打到主分支的時(shí)候提示很多編碼風(fēng)格的錯(cuò)誤問(wèn)題,后來(lái)重做了補(bǔ)丁才解決了問(wèn)題,這也是沒(méi)有嚴(yán)格按照的Linux編碼風(fēng)格從而導(dǎo)致的問(wèn)題。因?yàn)楫?dāng)時(shí)代碼量不大,所以解決問(wèn)題的時(shí)間相對(duì)較少。在代碼量增大的情況下可以借助工具進(jìn)行自動(dòng)修改。

          快速修改編碼風(fēng)格的工具

          scripts/checkpatch.pl

          這是一個(gè)檢查patch是否符合內(nèi)核編碼規(guī)范的腳本。默認(rèn)的調(diào)用也確實(shí)如此。如果用來(lái)檢查原文件,需要加上-f的選項(xiàng)。

          scripts/Lindent

          源碼路徑下的scripts目錄中的工具Lindent可以用來(lái)自動(dòng)修改縮進(jìn)問(wèn)題。不過(guò)使用Lindent要求系統(tǒng)安裝indent這個(gè)工具。Ubuntu系統(tǒng)下可以使用apt-get install indent進(jìn)行「安裝」。

          astyle

          比較推薦使用這個(gè)工具,因?yàn)楸容^相當(dāng)方便,可以一鍵式轉(zhuǎn)換成linuxjava,gnu等等風(fēng)格。

          下載地址項(xiàng)目地址文檔說(shuō)明

          如何使用,可以參考具體文檔說(shuō)明,寫(xiě)的比較詳細(xì)。

          總而言之,應(yīng)該顧全大局,在進(jìn)行內(nèi)核開(kāi)發(fā)和驅(qū)動(dòng)開(kāi)發(fā)的時(shí)候,嚴(yán)格遵守Linux的編碼規(guī)范,避免由于編碼不規(guī)范帶來(lái)的種種問(wèn)題,可以參考內(nèi)核路徑下Documentation/CodingStyle文檔,以下轉(zhuǎn)自Linux內(nèi)核文檔,最權(quán)威的文檔路徑,很全很強(qiáng)大,原來(lái)在這個(gè)網(wǎng)站上已經(jīng)有中文版了,感謝萬(wàn)分,具體的更新可以跳轉(zhuǎn)到你懂的網(wǎng)址。

          「來(lái)自 Documentation/process/coding-style.rst 的中文翻譯」

          Linux 內(nèi)核代碼風(fēng)格

          這是一個(gè)簡(jiǎn)短的文檔,描述了 linux 內(nèi)核的首選代碼風(fēng)格。代碼風(fēng)格是因人而異的, 而且我不愿意把自己的觀點(diǎn)強(qiáng)加給任何人,但這就像我去做任何事情都必須遵循的原則 那樣,我也希望在絕大多數(shù)事上保持這種的態(tài)度。請(qǐng) (在寫(xiě)代碼時(shí)) 至少考慮一下這里 的代碼風(fēng)格。

          首先,我建議你打印一份 GNU 代碼規(guī)范,然后不要讀。燒了它,這是一個(gè)具有重大象征性意義的動(dòng)作。

          不管怎樣,現(xiàn)在我們開(kāi)始:

          1 縮進(jìn)

          制表符是 8 個(gè)字符,所以縮進(jìn)也是 8 個(gè)字符。有些異端運(yùn)動(dòng)試圖將縮進(jìn)變?yōu)?4 (甚至 2!) 字符深,這幾乎相當(dāng)于嘗試將圓周率的值定義為 3。

          理由:縮進(jìn)的全部意義就在于清楚的定義一個(gè)控制塊起止于何處。尤其是當(dāng)你盯著你的 屏幕連續(xù)看了 20 小時(shí)之后,你將會(huì)發(fā)現(xiàn)大一點(diǎn)的縮進(jìn)會(huì)使你更容易分辨縮進(jìn)。

          現(xiàn)在,有些人會(huì)抱怨 8 個(gè)字符的縮進(jìn)會(huì)使代碼向右邊移動(dòng)的太遠(yuǎn),在 80 個(gè)字符的終端 屏幕上就很難讀這樣的代碼。這個(gè)問(wèn)題的答案是,如果你需要 3 級(jí)以上的縮進(jìn),不管用 何種方式你的代碼已經(jīng)有問(wèn)題了,應(yīng)該修正你的程序。

          簡(jiǎn)而言之,8 個(gè)字符的縮進(jìn)可以讓代碼更容易閱讀,還有一個(gè)好處是當(dāng)你的函數(shù)嵌套太 深的時(shí)候可以給你警告。留心這個(gè)警告。

          在 switch 語(yǔ)句中消除多級(jí)縮進(jìn)的首選的方式是讓 switch 和從屬于它的 case 標(biāo)簽對(duì)齊于同一列,而不要 兩次縮進(jìn) case 標(biāo)簽。比如:

          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;
          }

          不要把多個(gè)語(yǔ)句放在一行里,除非你有什么東西要隱藏:

          if?(condition)?do_this;
          ??do_something_everytime;

          也不要在一行里放多個(gè)賦值語(yǔ)句。內(nèi)核代碼風(fēng)格超級(jí)簡(jiǎn)單。就是避免可能導(dǎo)致別人誤讀 的表達(dá)式。

          除了注釋、文檔和 Kconfig 之外,不要使用空格來(lái)縮進(jìn),前面的例子是例外,是有意為 之。

          選用一個(gè)好的編輯器,不要在行尾留空格。

          2 把長(zhǎng)的行和字符串打散

          代碼風(fēng)格的意義就在于使用平常使用的工具來(lái)維持代碼的可讀性和可維護(hù)性。

          每一行的長(zhǎng)度的限制是 80 列,我們強(qiáng)烈建議您遵守這個(gè)慣例。

          長(zhǎng)于 80 列的語(yǔ)句要打散成有意義的片段。除非超過(guò) 80 列能顯著增加可讀性,并且不 會(huì)隱藏信息。子片段要明顯短于母片段,并明顯靠右。這同樣適用于有著很長(zhǎng)參數(shù)列表 的函數(shù)頭。然而,絕對(duì)不要打散對(duì)用戶(hù)可見(jiàn)的字符串,例如 printk 信息,因?yàn)檫@樣就 很難對(duì)它們 grep。

          3 大括號(hào)和空格的放置

          C 語(yǔ)言風(fēng)格中另外一個(gè)常見(jiàn)問(wèn)題是大括號(hào)的放置。和縮進(jìn)大小不同,選擇或棄用某種放 置策略并沒(méi)有多少技術(shù)上的原因,不過(guò)首選的方式,就像 Kernighan 和 Ritchie 展示 給我們的,是把起始大括號(hào)放在行尾,而把結(jié)束大括號(hào)放在行首,所以:

          if?(x?is?true)?{
          ????????we?do?y
          }

          這適用于所有的非函數(shù)語(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;
          }

          不過(guò),有一個(gè)例外,那就是函數(shù):函數(shù)的起始大括號(hào)放置于下一行的開(kāi)頭,所以:

          int?function(int?x)
          {
          ????????body?of?function
          }

          全世界的異端可能會(huì)抱怨這個(gè)不一致性是... 呃... 不一致的,不過(guò)所有思維健全的人 都知道 (a) K&R 是 「正確的」 并且 (b) K&R 是正確的。此外,不管怎樣函數(shù)都是特 殊的 (C 函數(shù)是不能嵌套的)。

          注意結(jié)束大括號(hào)獨(dú)自占據(jù)一行,除非它后面跟著同一個(gè)語(yǔ)句的剩余部分,也就是 do 語(yǔ) 句中的 “while” 或者 if 語(yǔ)句中的 “else”,像這樣:

          do?{
          ????????body?of?do-loop
          }?while?(condition);

          if?(x?==?y)?{
          ????????..
          }?else?if?(x?>?y)?{
          ????????...
          }?else?{
          ????????....
          }

          理由:K&R。

          也請(qǐng)注意這種大括號(hào)的放置方式也能使空 (或者差不多空的) 行的數(shù)量最小化,同時(shí)不 失可讀性。因此,由于你的屏幕上的新行是不可再生資源 (想想 25 行的終端屏幕),你 將會(huì)有更多的空行來(lái)放置注釋。

          當(dāng)只有一個(gè)單獨(dú)的語(yǔ)句的時(shí)候,不用加不必要的大括號(hào)。

          if?(condition)
          ????????action();

          if?(condition)
          ????????do_this();
          else
          ????????do_that();

          這并不適用于只有一個(gè)條件分支是單語(yǔ)句的情況;這時(shí)所有分支都要使用大括號(hào):

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

          3.1 空格

          Linux 內(nèi)核的空格使用方式 (主要) 取決于它是用于函數(shù)還是關(guān)鍵字。(大多數(shù)) 關(guān)鍵字 后要加一個(gè)空格。值得注意的例外是 sizeof, typeof, alignof 和 「attribute」,這 些關(guān)鍵字某些程度上看起來(lái)更像函數(shù) (它們?cè)?Linux 里也常常伴隨小括號(hào)而使用,盡管 在 C 里這樣的小括號(hào)不是必需的,就像 struct fileinfo info; 聲明過(guò)后的 sizeof info)。

          所以在這些關(guān)鍵字之后放一個(gè)空格:

          if,?switch,?case,?for,?do,?while

          但是不要在 sizeof, typeof, alignof 或者 「attribute」 這些關(guān)鍵字之后放空格。例如,

          s?=?sizeof(struct?file);

          不要在小括號(hào)里的表達(dá)式兩側(cè)加空格。這是一個(gè) 「反例」

          s?=?sizeof(?struct?file?);

          當(dāng)聲明指針類(lèi)型或者返回指針類(lèi)型的函數(shù)時(shí), * 的首選使用方式是使之靠近變量名 或者函數(shù)名,而不是靠近類(lèi)型名。例子:

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

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

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

          但是一元操作符后不要加空格:

          &??*??+??-??~??!??sizeof??typeof??alignof??__attribute__??defined

          后綴自加和自減一元操作符前不加空格:

          ++??--

          前綴自加和自減一元操作符后不加空格:

          ++??--

          .-> 結(jié)構(gòu)體成員操作符前后不加空格。

          不要在行尾留空白。有些可以自動(dòng)縮進(jìn)的編輯器會(huì)在新行的行首加入適量的空白,然后 你就可以直接在那一行輸入代碼。不過(guò)假如你最后沒(méi)有在那一行輸入代碼,有些編輯器 就不會(huì)移除已經(jīng)加入的空白,就像你故意留下一個(gè)只有空白的行。包含行尾空白的行就 這樣產(chǎn)生了。

          當(dāng) git 發(fā)現(xiàn)補(bǔ)丁包含了行尾空白的時(shí)候會(huì)警告你,并且可以應(yīng)你的要求去掉行尾空白;不過(guò)如果你是正在打一系列補(bǔ)丁,這樣做會(huì)導(dǎo)致后面的補(bǔ)丁失敗,因?yàn)槟愀淖兞搜a(bǔ)丁的 上下文。

          4 命名

          C 是一個(gè)簡(jiǎn)樸的語(yǔ)言,你的命名也應(yīng)該這樣。和 Modula-2 和 Pascal 程序員不同, C 程序員不使用類(lèi)似 ThisVariableIsATemporaryCounter 這樣華麗的名字。C 程序員會(huì) 稱(chēng)那個(gè)變量為 tmp ,這樣寫(xiě)起來(lái)會(huì)更容易,而且至少不會(huì)令其難于理解。

          不過(guò),雖然混用大小寫(xiě)的名字是不提倡使用的,但是全局變量還是需要一個(gè)具描述性的 名字。稱(chēng)一個(gè)全局函數(shù)為 foo 是一個(gè)難以饒恕的錯(cuò)誤。

          全局變量 (只有當(dāng)你 「真正」 需要它們的時(shí)候再用它) 需要有一個(gè)具描述性的名字,就 像全局函數(shù)。如果你有一個(gè)可以計(jì)算活動(dòng)用戶(hù)數(shù)量的函數(shù),你應(yīng)該叫它count_active_users() 或者類(lèi)似的名字,你不應(yīng)該叫它 cntuser()

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

          本地變量名應(yīng)該簡(jiǎn)短,而且能夠表達(dá)相關(guān)的含義。如果你有一些隨機(jī)的整數(shù)型的循環(huán)計(jì) 數(shù)器,它應(yīng)該被稱(chēng)為 i 。叫它 loop_counter 并無(wú)益處,如果它沒(méi)有被誤解的 可能的話。類(lèi)似的, tmp 可以用來(lái)稱(chēng)呼任意類(lèi)型的臨時(shí)變量。

          如果你怕混淆了你的本地變量名,你就遇到另一個(gè)問(wèn)題了,叫做函數(shù)增長(zhǎng)荷爾蒙失衡綜 合癥。請(qǐng)看第六章 (函數(shù))。

          5 Typedef

          不要使用類(lèi)似 vps_t 之類(lèi)的東西。

          對(duì)結(jié)構(gòu)體和指針使用 typedef 是一個(gè) 「錯(cuò)誤」 。當(dāng)你在代碼里看到:

          vps_t?a;

          這代表什么意思呢?

          相反,如果是這樣

          struct?virtual_container?*a;

          你就知道 a 是什么了。

          很多人認(rèn)為 typedef 能提高可讀性 。實(shí)際不是這樣的。它們只在下列情況下有用:

          1. 完全不透明的對(duì)象 (這種情況下要主動(dòng)使用 typedef 來(lái) 「隱藏」 這個(gè)對(duì)象實(shí)際上 是什么)。

            例如:pte_t 等不透明對(duì)象,你只能用合適的訪問(wèn)函數(shù)來(lái)訪問(wèn)它們。

            ?

            不透明性和 “訪問(wèn)函數(shù)” 本身是不好的。我們使用 pte_t 等類(lèi)型的原因在于真 的是完全沒(méi)有任何共用的可訪問(wèn)信息。

            ?
          2. 清楚的整數(shù)類(lèi)型,如此,這層抽象就可以 「幫助」 消除到底是 int 還是 long 的混淆。

            u8/u16/u32 是完全沒(méi)有問(wèn)題的 typedef,不過(guò)它們更符合類(lèi)別 (d) 而不是這里。

            ?

            要這樣做,必須事出有因。如果某個(gè)變量是 unsigned long ,那么沒(méi)有必要typedef unsigned long myflags_t;

            ?

            不過(guò)如果有一個(gè)明確的原因,比如它在某種情況下可能會(huì)是一個(gè) unsigned int 而在其他情況下可能為 unsigned long ,那么就不要猶豫,請(qǐng)務(wù)必使用 typedef。

          3. 當(dāng)你使用 sparse 按字面的創(chuàng)建一個(gè) 「新」 類(lèi)型來(lái)做類(lèi)型檢查的時(shí)候。

          4. 和標(biāo)準(zhǔn) C99 類(lèi)型相同的類(lèi)型,在某些例外的情況下。

            雖然讓眼睛和腦筋來(lái)適應(yīng)新的標(biāo)準(zhǔn)類(lèi)型比如 uint32_t 不需要花很多時(shí)間,可 是有些人仍然拒絕使用它們。

            因此,Linux 特有的等同于標(biāo)準(zhǔn)類(lèi)型的 u8/u16/u32/u64 類(lèi)型和它們的有符號(hào) 類(lèi)型是被允許的——盡管在你自己的新代碼中,它們不是強(qiáng)制要求要使用的。

            當(dāng)編輯已經(jīng)使用了某個(gè)類(lèi)型集的已有代碼時(shí),你應(yīng)該遵循那些代碼中已經(jīng)做出的選 擇。

          5. 可以在用戶(hù)空間安全使用的類(lèi)型。

            在某些用戶(hù)空間可見(jiàn)的結(jié)構(gòu)體里,我們不能要求 C99 類(lèi)型而且不能用上面提到的 u32 類(lèi)型。因此,我們?cè)谂c用戶(hù)空間共享的所有結(jié)構(gòu)體中使用 __u32 和類(lèi)似 的類(lèi)型。

          可能還有其他的情況,不過(guò)基本的規(guī)則是 「永遠(yuǎn)不要」 使用 typedef,除非你可以明 確的應(yīng)用上述某個(gè)規(guī)則中的一個(gè)。

          總的來(lái)說(shuō),如果一個(gè)指針或者一個(gè)結(jié)構(gòu)體里的元素可以合理的被直接訪問(wèn)到,那么它們 就不應(yīng)該是一個(gè) typedef。

          6 函數(shù)

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

          一個(gè)函數(shù)的最大長(zhǎng)度是和該函數(shù)的復(fù)雜度和縮進(jìn)級(jí)數(shù)成反比的。所以,如果你有一個(gè)理 論上很簡(jiǎn)單的只有一個(gè)很長(zhǎng) (但是簡(jiǎn)單) 的 case 語(yǔ)句的函數(shù),而且你需要在每個(gè) case 里做很多很小的事情,這樣的函數(shù)盡管很長(zhǎng),但也是可以的。

          不過(guò),如果你有一個(gè)復(fù)雜的函數(shù),而且你懷疑一個(gè)天分不是很高的高中一年級(jí)學(xué)生可能 甚至搞不清楚這個(gè)函數(shù)的目的,你應(yīng)該嚴(yán)格遵守前面提到的長(zhǎng)度限制。使用輔助函數(shù), 并為之取個(gè)具描述性的名字 (如果你覺(jué)得它們的性能很重要的話,可以讓編譯器內(nèi)聯(lián)它 們,這樣的效果往往會(huì)比你寫(xiě)一個(gè)復(fù)雜函數(shù)的效果要好。)

          函數(shù)的另外一個(gè)衡量標(biāo)準(zhǔn)是本地變量的數(shù)量。此數(shù)量不應(yīng)超過(guò) 5-10 個(gè),否則你的函數(shù) 就有問(wèn)題了。重新考慮一下你的函數(shù),把它分拆成更小的函數(shù)。人的大腦一般可以輕松 的同時(shí)跟蹤 7 個(gè)不同的事物,如果再增多的話,就會(huì)糊涂了。即便你聰穎過(guò)人,你也可 能會(huì)記不清你 2 個(gè)星期前做過(guò)的事情。

          在源文件里,使用空行隔開(kāi)不同的函數(shù)。如果該函數(shù)需要被導(dǎo)出,它的 「EXPORT」 宏 應(yīng)該緊貼在它的結(jié)束大括號(hào)之下。比如:

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

          在函數(shù)原型中,包含函數(shù)名和它們的數(shù)據(jù)類(lèi)型。雖然 C 語(yǔ)言里沒(méi)有這樣的要求,在 Linux 里這是提倡的做法,因?yàn)檫@樣可以很簡(jiǎn)單的給讀者提供更多的有價(jià)值的信息。

          7 集中的函數(shù)退出途徑

          雖然被某些人聲稱(chēng)已經(jīng)過(guò)時(shí),但是 goto 語(yǔ)句的等價(jià)物還是經(jīng)常被編譯器所使用,具體 形式是無(wú)條件跳轉(zhuǎn)指令。

          當(dāng)一個(gè)函數(shù)從多個(gè)位置退出,并且需要做一些類(lèi)似清理的常見(jiàn)操作時(shí),goto 語(yǔ)句就很方 便了。如果并不需要清理操作,那么直接 return 即可。

          選擇一個(gè)能夠說(shuō)明 goto 行為或它為何存在的標(biāo)簽名。如果 goto 要釋放 buffer, 一個(gè)不錯(cuò)的名字可以是 out_free_buffer: 。別去使用像 err1:err2: 這樣的GW_BASIC 名稱(chēng),因?yàn)橐坏┠闾砑踊騽h除了 (函數(shù)的) 退出路徑,你就必須對(duì)它們 重新編號(hào),這樣會(huì)難以去檢驗(yàn)正確性。

          使用 goto 的理由是:

          • 無(wú)條件語(yǔ)句容易理解和跟蹤
          • 嵌套程度減小
          • 可以避免由于修改時(shí)忘記更新個(gè)別的退出點(diǎn)而導(dǎo)致錯(cuò)誤
          • 讓編譯器省去刪除冗余代碼的工作 ;)
          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;
          }

          一個(gè)需要注意的常見(jiàn)錯(cuò)誤是 一個(gè) err 錯(cuò)誤 ,就像這樣:

          err:
          ????????kfree(foo->bar);
          ????????kfree(foo);
          ????????return?ret;

          這段代碼的錯(cuò)誤是,在某些退出路徑上 foo 是 NULL。通常情況下,通過(guò)把它分離 成兩個(gè)錯(cuò)誤標(biāo)簽 err_free_bar:err_free_foo: 來(lái)修復(fù)這個(gè)錯(cuò)誤:

          err_free_bar:
          ???????kfree(foo->bar);
          err_free_foo:
          ???????kfree(foo);
          ???????return?ret;

          理想情況下,你應(yīng)該模擬錯(cuò)誤來(lái)測(cè)試所有退出路徑。

          8 注釋

          注釋是好的,不過(guò)有過(guò)度注釋的危險(xiǎn)。永遠(yuǎn)不要在注釋里解釋你的代碼是如何運(yùn)作的:更好的做法是讓別人一看你的代碼就可以明白,解釋寫(xiě)的很差的代碼是浪費(fèi)時(shí)間。

          一般的,你想要你的注釋告訴別人你的代碼做了什么,而不是怎么做的。也請(qǐng)你不要把 注釋放在一個(gè)函數(shù)體內(nèi)部:如果函數(shù)復(fù)雜到你需要獨(dú)立的注釋其中的一部分,你很可能 需要回到第六章看一看。你可以做一些小注釋來(lái)注明或警告某些很聰明 (或者槽糕) 的 做法,但不要加太多。你應(yīng)該做的,是把注釋放在函數(shù)的頭部,告訴人們它做了什么, 也可以加上它做這些事情的原因。

          當(dāng)注釋內(nèi)核 API 函數(shù)時(shí),請(qǐng)使用 kernel-doc 格式。請(qǐng)看 Documentation/doc-guide/ 和 scripts/kernel-doc 以獲得詳細(xì)信息。

          長(zhǎng) (多行) 注釋的首選風(fēng)格是:

          /*
          ?*?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.
          ?*/

          對(duì)于在 net/ 和 drivers/net/ 的文件,首選的長(zhǎng) (多行) 注釋風(fēng)格有些不同。

          /*?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.
          ?*/

          注釋數(shù)據(jù)也是很重要的,不管是基本類(lèi)型還是衍生類(lèi)型。為了方便實(shí)現(xiàn)這一點(diǎn),每一行 應(yīng)只聲明一個(gè)數(shù)據(jù) (不要使用逗號(hào)來(lái)一次聲明多個(gè)數(shù)據(jù))。這樣你就有空間來(lái)為每個(gè)數(shù)據(jù) 寫(xiě)一段小注釋來(lái)解釋它們的用途了。

          9 你已經(jīng)把事情弄糟了

          這沒(méi)什么,我們都是這樣??赡苣愕氖褂昧撕荛L(zhǎng)時(shí)間 Unix 的朋友已經(jīng)告訴你 GNU emacs 能自動(dòng)幫你格式化 C 源代碼,而且你也注意到了,確實(shí)是這樣,不過(guò)它 所使用的默認(rèn)值和我們想要的相去甚遠(yuǎn) (實(shí)際上,甚至比隨機(jī)打的還要差——無(wú)數(shù)個(gè)猴子 在 GNU emacs 里打字永遠(yuǎn)不會(huì)創(chuàng)造出一個(gè)好程序) (譯注:Infinite Monkey Theorem)

          所以你要么放棄 GNU emacs,要么改變它讓它使用更合理的設(shè)定。要采用后一個(gè)方案, 你可以把下面這段粘貼到你的 .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")))))

          這會(huì)讓 emacs 在 ~/src/linux-trees 下的 C 源文件獲得更好的內(nèi)核代碼風(fēng)格。

          不過(guò)就算你嘗試讓 emacs 正確的格式化代碼失敗了,也并不意味著你失去了一切:還可 以用 indent

          不過(guò),GNU indent 也有和 GNU emacs 一樣有問(wèn)題的設(shè)定,所以你需要給它一些命令選 項(xiàng)。不過(guò),這還不算太糟糕,因?yàn)榫退闶?GNU indent 的作者也認(rèn)同 K&R 的權(quán)威性 (GNU 的人并不是壞人,他們只是在這個(gè)問(wèn)題上被嚴(yán)重的誤導(dǎo)了),所以你只要給 indent 指定選項(xiàng) -kr -i8 (代表 K&R,8 字符縮進(jìn)),或使用 scripts/Lindent 這樣就可以以最時(shí)髦的方式縮進(jìn)源代碼。

          indent 有很多選項(xiàng),特別是重新格式化注釋的時(shí)候,你可能需要看一下它的手冊(cè)。不過(guò)記住:indent 不能修正壞的編程習(xí)慣。

          10 Kconfig 配置文件

          對(duì)于遍布源碼樹(shù)的所有 Kconfig* 配置文件來(lái)說(shuō),它們縮進(jìn)方式有所不同。緊挨著 config 定義的行,用一個(gè)制表符縮進(jìn),然而 help 信息的縮進(jìn)則額外增加 2 個(gè)空 格。舉個(gè)例子:

          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.

          而那些危險(xiǎn)的功能 (比如某些文件系統(tǒng)的寫(xiě)支持) 應(yīng)該在它們的提示字符串里顯著的聲 明這一點(diǎn):

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

          要查看配置文件的完整文檔,請(qǐng)看 Documentation/kbuild/kconfig-language.txt。

          11 數(shù)據(jù)結(jié)構(gòu)

          如果一個(gè)數(shù)據(jù)結(jié)構(gòu),在創(chuàng)建和銷(xiāo)毀它的單線執(zhí)行環(huán)境之外可見(jiàn),那么它必須要有一個(gè)引 用計(jì)數(shù)器。內(nèi)核里沒(méi)有垃圾收集 (并且內(nèi)核之外的垃圾收集慢且效率低下),這意味著你 絕對(duì)需要記錄你對(duì)這種數(shù)據(jù)結(jié)構(gòu)的使用情況。

          引用計(jì)數(shù)意味著你能夠避免上鎖,并且允許多個(gè)用戶(hù)并行訪問(wèn)這個(gè)數(shù)據(jù)結(jié)構(gòu)——而不需要 擔(dān)心這個(gè)數(shù)據(jù)結(jié)構(gòu)僅僅因?yàn)闀簳r(shí)不被使用就消失了,那些用戶(hù)可能不過(guò)是沉睡了一陣或 者做了一些其他事情而已。

          注意上鎖 「不能」 取代引用計(jì)數(shù)。上鎖是為了保持?jǐn)?shù)據(jù)結(jié)構(gòu)的一致性,而引用計(jì)數(shù)是一 個(gè)內(nèi)存管理技巧。通常二者都需要,不要把兩個(gè)搞混了。

          很多數(shù)據(jù)結(jié)構(gòu)實(shí)際上有 2 級(jí)引用計(jì)數(shù),它們通常有不同 類(lèi) 的用戶(hù)。子類(lèi)計(jì)數(shù)器統(tǒng) 計(jì)子類(lèi)用戶(hù)的數(shù)量,每當(dāng)子類(lèi)計(jì)數(shù)器減至零時(shí),全局計(jì)數(shù)器減一。

          這種 多級(jí)引用計(jì)數(shù) 的例子可以在內(nèi)存管理 (struct mm_struct: mm_users 和 mm_count),和文件系統(tǒng) (struct super_block: s_count 和 s_active) 中找到。

          記?。喝绻硪粋€(gè)執(zhí)行線索可以找到你的數(shù)據(jù)結(jié)構(gòu),但這個(gè)數(shù)據(jù)結(jié)構(gòu)沒(méi)有引用計(jì)數(shù)器, 這里幾乎肯定是一個(gè) bug。

          12 宏,枚舉和RTL

          用于定義常量的宏的名字及枚舉里的標(biāo)簽需要大寫(xiě)。

          #define?CONSTANT?0x12345

          在定義幾個(gè)相關(guān)的常量時(shí),最好用枚舉。

          宏的名字請(qǐng)用大寫(xiě)字母,不過(guò)形如函數(shù)的宏的名字可以用小寫(xiě)字母。

          一般的,如果能寫(xiě)成內(nèi)聯(lián)函數(shù)就不要寫(xiě)成像函數(shù)的宏。

          含有多個(gè)語(yǔ)句的宏應(yīng)該被包含在一個(gè) do-while 代碼塊里:

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

          使用宏的時(shí)候應(yīng)避免的事情:

          1. 影響控制流程的宏:
          #define?FOO(x)??????????????????????????????????\
          ????????do?{????????????????????????????????????\
          ????????????????if?(blah(x)?????????????????????????return?-EBUGGERED;??????\
          ????????}?while?(0)

          「非?!?/strong> 不好。它看起來(lái)像一個(gè)函數(shù),不過(guò)卻能導(dǎo)致 調(diào)用 它的函數(shù)退出;不要打 亂讀者大腦里的語(yǔ)法分析器。

          1. 依賴(lài)于一個(gè)固定名字的本地變量的宏:
          #define?FOO(val)?bar(index,?val)

          可能看起來(lái)像是個(gè)不錯(cuò)的東西,不過(guò)它非常容易把讀代碼的人搞糊涂,而且容易導(dǎo)致看起 來(lái)不相關(guān)的改動(dòng)帶來(lái)錯(cuò)誤。

          1. 作為左值的帶參數(shù)的宏:FOO(x) = y;如果有人把 FOO 變成一個(gè)內(nèi)聯(lián)函數(shù)的話,這 種用法就會(huì)出錯(cuò)了。
          2. 忘記了優(yōu)先級(jí):使用表達(dá)式定義常量的宏必須將表達(dá)式置于一對(duì)小括號(hào)之內(nèi)。帶參數(shù) 的宏也要注意此類(lèi)問(wèn)題。
          #define?CONSTANT?0x4000
          #define?CONSTEXP?(CONSTANT?|?3)
          1. 在宏里定義類(lèi)似函數(shù)的本地變量時(shí)命名沖突:
          #define?FOO(x)??????????????????????????\
          ({??????????????????????????????????????\
          ????????typeof(x)?ret;??????????????????\
          ????????ret?=?calc_ret(x);??????????????\
          ????????(ret);??????????????????????????\
          })

          ret 是本地變量的通用名字 - __foo_ret 更不容易與一個(gè)已存在的變量沖突。

          cpp 手冊(cè)對(duì)宏的講解很詳細(xì)。gcc internals 手冊(cè)也詳細(xì)講解了 RTL,內(nèi)核里的匯編語(yǔ) 言經(jīng)常用到它。

          13 打印內(nèi)核消息

          內(nèi)核開(kāi)發(fā)者應(yīng)該是受過(guò)良好教育的。請(qǐng)一定注意內(nèi)核信息的拼寫(xiě),以給人以好的印象。不要用不規(guī)范的單詞比如 dont,而要用 do not 或者 don't 。保證這些信 息簡(jiǎn)單明了,無(wú)歧義。

          內(nèi)核信息不必以英文句號(hào)結(jié)束。

          在小括號(hào)里打印數(shù)字 (%d) 沒(méi)有任何價(jià)值,應(yīng)該避免這樣做。

          里有一些驅(qū)動(dòng)模型診斷宏,你應(yīng)該使用它們,以確保信息對(duì)應(yīng)于正確 的設(shè)備和驅(qū)動(dòng),并且被標(biāo)記了正確的消息級(jí)別。這些宏有:dev_err(), dev_warn(), dev_info() 等等。對(duì)于那些不和某個(gè)特定設(shè)備相關(guān)連的信息, 定義了 pr_notice(), pr_info(), pr_warn(), pr_err()和其他。

          寫(xiě)出好的調(diào)試信息可以是一個(gè)很大的挑戰(zhàn);一旦你寫(xiě)出后,這些信息在遠(yuǎn)程除錯(cuò)時(shí)能提 供極大的幫助。然而打印調(diào)試信息的處理方式同打印非調(diào)試信息不同。其他 pr_XXX() 函數(shù)能無(wú)條件地打印,pr_debug() 卻不;默認(rèn)情況下它不會(huì)被編譯,除非定義了 DEBUG 或設(shè)定了 CONFIG_DYNAMIC_DEBUG。實(shí)際這同樣是為了 dev_dbg(),一個(gè)相關(guān)約定是在一 個(gè)已經(jīng)開(kāi)啟了 DEBUG 時(shí),使用 VERBOSE_DEBUG 來(lái)添加 dev_vdbg()。

          許多子系統(tǒng)擁有 Kconfig 調(diào)試選項(xiàng)來(lái)開(kāi)啟 -DDEBUG 在對(duì)應(yīng)的 Makefile 里面;在其他 情況下,特殊文件使用 #define DEBUG。當(dāng)一條調(diào)試信息需要被無(wú)條件打印時(shí),例如, 如果已經(jīng)包含一個(gè)調(diào)試相關(guān)的 #ifdef 條件,printk(KERN_DEBUG ...) 就可被使用。

          14 分配內(nèi)存

          內(nèi)核提供了下面的一般用途的內(nèi)存分配函數(shù):kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc()vzalloc()。請(qǐng)參考 API 文檔以獲取有關(guān)它們的詳細(xì)信息。

          傳遞結(jié)構(gòu)體大小的首選形式是這樣的:

          p?=?kmalloc(sizeof(*p),?...);

          另外一種傳遞方式中,sizeof 的操作數(shù)是結(jié)構(gòu)體的名字,這樣會(huì)降低可讀性,并且可能 會(huì)引入 bug。有可能指針變量類(lèi)型被改變時(shí),而對(duì)應(yīng)的傳遞給內(nèi)存分配函數(shù)的 sizeof 的結(jié)果不變。

          強(qiáng)制轉(zhuǎn)換一個(gè) void 指針?lè)祷刂凳嵌嘤嗟?。C 語(yǔ)言本身保證了從 void 指針到其他任何 指針類(lèi)型的轉(zhuǎn)換是沒(méi)有問(wèn)題的。

          分配一個(gè)數(shù)組的首選形式是這樣的:

          p?=?kmalloc_array(n,?sizeof(...),?...);

          分配一個(gè)零長(zhǎng)數(shù)組的首選形式是這樣的:

          p?=?kcalloc(n,?sizeof(...),?...);

          兩種形式檢查分配大小 n * sizeof(...) 的溢出,如果溢出返回 NULL。

          15 內(nèi)聯(lián)弊病

          有一個(gè)常見(jiàn)的誤解是 內(nèi)聯(lián) 是 gcc 提供的可以讓代碼運(yùn)行更快的一個(gè)選項(xiàng)。雖然使用內(nèi)聯(lián)函數(shù)有時(shí)候是恰當(dāng)?shù)?(比如作為一種替代宏的方式,請(qǐng)看第十二章),不過(guò)很多情 況下不是這樣。「inline 的過(guò)度使用會(huì)使內(nèi)核變大」,從而使整個(gè)系統(tǒng)運(yùn)行速度變慢。因?yàn)轶w積大內(nèi)核會(huì)占用更多的指令高速緩存,而且會(huì)導(dǎo)致 pagecache 的可用內(nèi)存減少。想象一下,一次 pagecache 未命中就會(huì)導(dǎo)致一次磁盤(pán)尋址,將耗時(shí) 5 毫秒。

          「5 毫秒的 時(shí)間內(nèi) CPU 能執(zhí)行很多很多指令?!?/strong>

          一個(gè)基本的原則是如果一個(gè)函數(shù)有 3 行以上,就不要把它變成內(nèi)聯(lián)函數(shù)。這個(gè)原則的一 個(gè)例外是,如果你知道某個(gè)參數(shù)是一個(gè)編譯時(shí)常量,而且因?yàn)檫@個(gè)常量你確定編譯器在 編譯時(shí)能優(yōu)化掉你的函數(shù)的大部分代碼,那仍然可以給它加上 inline 關(guān)鍵字。kmalloc() 內(nèi)聯(lián)函數(shù)就是一個(gè)很好的例子。

          人們經(jīng)常主張給 static 的而且只用了一次的函數(shù)加上 inline,如此不會(huì)有任何損失, 因?yàn)闆](méi)有什么好權(quán)衡的。雖然從技術(shù)上說(shuō)這是正確的,但是實(shí)際上這種情況下即使不加 inline gcc 也可以自動(dòng)使其內(nèi)聯(lián)。而且其他用戶(hù)可能會(huì)要求移除 inline,由此而來(lái)的 爭(zhēng)論會(huì)抵消 inline 自身的潛在價(jià)值,得不償失。

          16 函數(shù)返回值及命名

          函數(shù)可以返回多種不同類(lèi)型的值,最常見(jiàn)的一種是表明函數(shù)執(zhí)行成功或者失敗的值。這樣 的一個(gè)值可以表示為一個(gè)錯(cuò)誤代碼整數(shù) (-Exxx=失敗,0=成功) 或者一個(gè) 成功 布爾值 (0=失敗,非0=成功)。

          混合使用這兩種表達(dá)方式是難于發(fā)現(xiàn)的 bug 的來(lái)源。如果 C 語(yǔ)言本身嚴(yán)格區(qū)分整形和 布爾型變量,那么編譯器就能夠幫我們發(fā)現(xiàn)這些錯(cuò)誤... 不過(guò) C 語(yǔ)言不區(qū)分。為了避免 產(chǎn)生這種 bug,請(qǐng)遵循下面的慣例:

          如果函數(shù)的名字是一個(gè)動(dòng)作或者強(qiáng)制性的命令,那么這個(gè)函數(shù)應(yīng)該返回錯(cuò)誤代
          碼整數(shù)。如果是一個(gè)判斷,那么函數(shù)應(yīng)該返回一個(gè)?"成功"?布爾值。

          比如, add work 是一個(gè)命令,所以 add_work() 在成功時(shí)返回 0,在失敗時(shí)返回 -EBUSY。類(lèi)似的,因?yàn)?PCI device present 是一個(gè)判斷,所以 pci_dev_present() 在成功找到一個(gè)匹配的設(shè)備時(shí)應(yīng)該返回 1,如果找不到時(shí)應(yīng)該返回 0。

          所有 EXPORTed 函數(shù)都必須遵守這個(gè)慣例,所有的公共函數(shù)也都應(yīng)該如此。私有 (static) 函數(shù)不需要如此,但是我們也推薦這樣做。

          返回值是實(shí)際計(jì)算結(jié)果而不是計(jì)算是否成功的標(biāo)志的函數(shù)不受此慣例的限制。一般的, 他們通過(guò)返回一些正常值范圍之外的結(jié)果來(lái)表示出錯(cuò)。典型的例子是返回指針的函數(shù), 他們使用 NULL 或者 ERR_PTR 機(jī)制來(lái)報(bào)告錯(cuò)誤。

          17 不要重新發(fā)明內(nèi)核宏

          頭文件 include/linux/kernel.h 包含了一些宏,你應(yīng)該使用它們,而不要自己寫(xiě)一些 它們的變種。比如,如果你需要計(jì)算一個(gè)數(shù)組的長(zhǎng)度,使用這個(gè)宏

          #define?ARRAY_SIZE(x)?(sizeof(x)?/?sizeof((x)[0]))

          類(lèi)似的,如果你要計(jì)算某結(jié)構(gòu)體成員的大小,使用

          #define?FIELD_SIZEOF(t,?f)?(sizeof(((t*)0)->f))

          還有可以做嚴(yán)格的類(lèi)型檢查的 min() 和 max() 宏,如果你需要可以使用它們。你可以 自己看看那個(gè)頭文件里還定義了什么你可以拿來(lái)用的東西,如果有定義的話,你就不應(yīng) 在你的代碼里自己重新定義。

          18 編輯器模式行和其他需要羅嗦的事情

          有一些編輯器可以解釋嵌入在源文件里的由一些特殊標(biāo)記標(biāo)明的配置信息。比如,emacs 能夠解釋被標(biāo)記成這樣的行:

          -*-?mode:?c?-*-

          或者這樣的:

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

          Vim 能夠解釋這樣的標(biāo)記:

          /*?vim:set?sw=8?noet?*/

          不要在源代碼中包含任何這樣的內(nèi)容。

          每個(gè)人都有他自己的編輯器配置,你的源文件不 應(yīng)該覆蓋別人的配置。這包括有關(guān)縮進(jìn)和模式配置的標(biāo)記。人們可以使用他們自己定制 的模式,或者使用其他可以產(chǎn)生正確的縮進(jìn)的巧妙方法。

          19 內(nèi)聯(lián)匯編

          在特定架構(gòu)的代碼中,你可能需要內(nèi)聯(lián)匯編與 CPU 和平臺(tái)相關(guān)功能連接。需要這么做時(shí) 就不要猶豫。然而,當(dāng) C 可以完成工作時(shí),不要平白無(wú)故地使用內(nèi)聯(lián)匯編。在可能的情 況下,你可以并且應(yīng)該用 C 和硬件溝通。

          請(qǐng)考慮去寫(xiě)捆綁通用位元 (wrap common bits) 的內(nèi)聯(lián)匯編的簡(jiǎn)單輔助函數(shù),別去重復(fù) 地寫(xiě)下只有細(xì)微差異內(nèi)聯(lián)匯編。記住內(nèi)聯(lián)匯編可以使用 C 參數(shù)。

          大型,有一定復(fù)雜度的匯編函數(shù)應(yīng)該放在 .S 文件內(nèi),用相應(yīng)的 C 原型定義在 C 頭文 件中。匯編函數(shù)的 C 原型應(yīng)該使用 asmlinkage 。

          你可能需要把匯編語(yǔ)句標(biāo)記為 volatile,用來(lái)阻止 GCC 在沒(méi)發(fā)現(xiàn)任何副作用后就把它 移除了。你不必總是這樣做,盡管,這不必要的舉動(dòng)會(huì)限制優(yōu)化。

          在寫(xiě)一個(gè)包含多條指令的單個(gè)內(nèi)聯(lián)匯編語(yǔ)句時(shí),把每條指令用引號(hào)分割而且各占一行, 除了最后一條指令外,在每個(gè)指令結(jié)尾加上 nt,讓匯編輸出時(shí)可以正確地縮進(jìn)下一條 指令:

          asm?("magic?%reg1,?#42\n\t"
          ?????"more_magic?%reg2,?%reg3"
          ?????:?/*?outputs?*/?:?/*?inputs?*/?:?/*?clobbers?*/);

          20 條件編譯

          只要可能,就不要在 .c 文件里面使用預(yù)處理?xiàng)l件 (#if, #ifdef);這樣做讓代碼更難 閱讀并且更難去跟蹤邏輯。替代方案是,在頭文件中用預(yù)處理?xiàng)l件提供給那些 .c 文件 使用,再給 #else 提供一個(gè)空樁 (no-op stub) 版本,然后在 .c 文件內(nèi)無(wú)條件地調(diào)用 那些 (定義在頭文件內(nèi)的) 函數(shù)。這樣做,編譯器會(huì)避免為樁函數(shù) (stub) 的調(diào)用生成 任何代碼,產(chǎn)生的結(jié)果是相同的,但邏輯將更加清晰。

          最好傾向于編譯整個(gè)函數(shù),而不是函數(shù)的一部分或表達(dá)式的一部分。與其放一個(gè) ifdef 在表達(dá)式內(nèi),不如分解出部分或全部表達(dá)式,放進(jìn)一個(gè)單獨(dú)的輔助函數(shù),并應(yīng)用預(yù)處理 條件到這個(gè)輔助函數(shù)內(nèi)。

          如果你有一個(gè)在特定配置中,可能變成未使用的函數(shù)或變量,編譯器會(huì)警告它定義了但 未使用,把它標(biāo)記為 __maybe_unused 而不是將它包含在一個(gè)預(yù)處理?xiàng)l件中。(然而,如 果一個(gè)函數(shù)或變量總是未使用,就直接刪除它。)

          在代碼中,盡可能地使用 IS_ENABLED 宏來(lái)轉(zhuǎn)化某個(gè) Kconfig 標(biāo)記為 C 的布爾 表達(dá)式,并在一般的 C 條件中使用它:

          if?(IS_ENABLED(CONFIG_SOMETHING))?{
          ????????...
          }

          編譯器會(huì)做常量折疊,然后就像使用 #ifdef 那樣去包含或排除代碼塊,所以這不會(huì)帶 來(lái)任何運(yùn)行時(shí)開(kāi)銷(xiāo)。然而,這種方法依舊允許 C 編譯器查看塊內(nèi)的代碼,并檢查它的正 確性 (語(yǔ)法,類(lèi)型,符號(hào)引用,等等)。因此,如果條件不滿(mǎn)足,代碼塊內(nèi)的引用符號(hào)就 不存在時(shí),你還是必須去用 #ifdef。

          在任何有意義的 #if 或 #ifdef 塊的末尾 (超過(guò)幾行的),在 #endif 同一行的后面寫(xiě)下 注解,注釋這個(gè)條件表達(dá)式。例如:

          #ifdef?CONFIG_SOMETHING
          ...
          #endif?/*?CONFIG_SOMETHING?*/


          —— The End?—

          推薦好文??點(diǎn)擊藍(lán)色字體即可跳轉(zhuǎn)
          ??感覺(jué)身體被掏空!只因?yàn)楦瘟诉@篇空間矢量控制算法
          ??當(dāng)心!別再被大小端的問(wèn)題坑了
          ??PID微分器與濾波器的愛(ài)恨情仇
          ??簡(jiǎn)易PID算法的快速掃盲
          ??增量式PID到底是什么?
          ??三面大疆慘敗,因?yàn)椴欢甈ID的積分抗飽和

          原創(chuàng)不易,歡迎轉(zhuǎn)發(fā)、留言、點(diǎn)贊、分享給你的朋友,感謝您的支持!


          長(zhǎng)按識(shí)別二維碼關(guān)注獲取更多內(nèi)容

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    久久久成人免费电影 | 人人超碰国产五月天 | 岳乳丰满一区二区三区 | 色情一级A片成人片 | 国产精品久久久久久久久久久久久久久久 |