linux内核首选编码样式的文档

电子说

1.2w人已加入

描述

每个人都有自己的编码风格,我不会将我的观点强加到任何人的身上,但这正是我所要保持的东西,就像其他许多事情一样。至少请考虑在这里所列出的观点。

首先,我建议打印出GNU编码标准的副本,不要去阅读,直接将它烧毁。这是一个伟大的象征性的姿态。

好,现在正式开始:

第1章:缩进

T一个Tab键有8个字符位因此一个缩进也是8个字符位 . 有人试图将一个缩进定义场4个字符位甚至2个 , 这无异于试图将Pi的值定义为3.

说明: 缩进的意义在于定义语句块的开始和结尾 .尤其是当你紧盯屏幕20小时后你就更加能体会到缩进的作用了 .

现在有些人说8字符长的缩进使得代码太靠右边,且很难在80字符的终端窗口阅读。事实是,如果你的代码需要三层以上的缩进,这是你犯糊涂了,你得修改你的程序。

总之,8字符缩进会让程序更好阅读,而而外的好处则是能够提醒你你的程序嵌套的太深了。这个需要警惕。

精简 switch 语句中缩进界级别的最好方式就是将"switch"和其下级的"case"放在同一列,而不是双重缩进"case"标签。例如:  

C语言

不要将多个语句放在同一行,除非你要掩饰什么:

C语言

同样也不要将多个赋值放在同一行。内核编程范式超简单。避免复杂的表达式。 

还有一个潜规则,是 Kconfig 推荐的,永远不要用空格缩进,上面的例子是故意的。找一个合适的编辑器,记得不要在行末留有空格。 

第二章:换行  编程范式的意义在于使用常见工具时的可阅读和可维护性。 行的长度限制为80列,这是强烈推荐的设置。 

多于80列的语句将被分为合适的数块。子句应该永远比主句短,且比主句更靠右侧。这对有较长参数表的函数声明同样有效。长字符串同样也被打断为短字符串。这样做能在超过80列时提高可阅读性,且不会隐藏信息。

C语言

第三章:大括号和空格 

关于 C 风格编程的另一个议点就是大括号。跟缩进值不同,大括号的放置并没有技术因素在内,但“先贤“ Kernighan 和 Ritchie 展示为我们的最好方式,是将左大括号放置在行末尾。而右大括号放置在行首,如下:

C语言

对非函数的语句块也是如此(if、switch、for、while、do)。例如:

C语言

当然那,有一个特殊的例外, 即函数:它的左大括号在新行的行首,如下:

C语言

全世界有不同意见的人都认为此不一致的地方——好吧——很是缺乏一致性,不过只要是能正常思考的都知道《C程序设计语言(第一版)》中是 right 而《C程序设计语言(第二版)》中则是 right。无论如何,函数都是特殊的(在 C 语言中你不能嵌套定义它们)。

注意,右大括号单独一行, 除非后面跟着的是同一个语句。例如 do 语句中的 "while"或 if 语句中的 "else" 如下:

C语言

和 

C语言

阐述:K&R, 《C程序设计语言》一书的缩写。

同时要注意,这种放置大括号的方式能够在不影响可读性的同时,有效减少空行(或近乎空行)的数量。因此,你不许要刷新资源就可以看倒更多行(想想一下只能显示25行的终端窗口),且你能够有空多的空行来安置注释。

只有一行语句时不用添加多余的大括号。

C语言

C语言

判断语句中只有一个分支为单行语句是,不支持这样用,你需要在所有分支中都加入大括号。

C语言

3.1:空格

linux内核风格下的空格,其实际应用主要是取决于函数标识符的使用。大多数函数在其标识符后面会加上空格。当然,sizeof, typeof, alignof, 和 __attribute__,像这样的长得像函数(但是因为不需要,所以经常屁股后面不跟着圆括号。例如:"sizeof info" 如果在 "struct fileinfo info;"这个声明之后……木有括号……)的,就不需要加空格了。

因此你就知道,在如下关键字后面加个空格:

if, switch, case, for, do, while

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

C语言

当声明指针或者声明一个返回指针的函数时,最好是在把个小小的“*”离着标识符(不论是变量常量还是函数)近一点,而不是和数据类型近一点,如:

C语言

在双目或者三目运算符周围(左边和右边)用上空格——他们包括(译者注:可能不止,并未对此进行仔细检查):

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

但是单目运算符就算了,比如这么几个家伙(译者注:同上,未作检查):

&  *  +  -  ~  !  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语言

另外,在函数原型中,当声明参量时应该将参量的类型和标识符放到一起,虽然c语言本身并不要求这种形式,但是在linux kernal里面是要求的——目的是增加每一行代码的信息量。

章7:集中于一处退出函数

虽然很多人不提倡,但是我们这里要经常使用goto进行无条件的跳转。

当函数有很多个出口,使用goto把这些出口集中到一处是很方便的,特别是函数中有许多重复的清理工作的时候。

理由是:

-无条件跳转易于理解

-可以减少嵌套

-可以避免那种忘记更新某一个出口点的问题

-算是帮助编译器做了代码优化

C语言

第8章:注释

有注释当然是好的,但是注释太多就很恶心了。千万不要在注释里面解释你的程序怎么运行的。相对于尝试用注释解释清楚你那恶心的代码,你还不如就写个清晰易懂(译者注:就是小而精,萌妹子一样单纯的~)的函数。

一般的,你的注释是用来说明这段代码是“干啥的”,而不是“怎么干的”。另外,别把注释放到你的函数体里面(译者注:把我放到妹子的怀抱里吧!):如过你的函数确实复杂到需要用注释来分隔成几段,回第六章擦亮眼睛再看两遍(译者注:那一段正好也是我翻译的)。以可以用几个小注释来提醒大家一些东西(“写的好~”或者“写的真tm糟糕”),但是就不要自取其辱评论自己的东西了……最后,仍然提醒你,一定是把注释安放在函数的前面,然后简单写下这段函数式干啥的,如果可能,你倒是不妨提及为什么你要这样做。

当给kernal api函数注释的时候,请使用kernal-doc格式。该格式的细节你可以参考这两个文件:Documentation/kernel-doc-nano-HOWTO.txt和scripts/kernel-doc(译者注:这个文件路径应当是指内核源码中的路径)

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

多于一行(多行)的注释应当准从以下格式:

C语言

该格式对于注释标识符(常量,变量,函数等)同样适用。换句话说,你最好不要再一行里面同时声明很多个标识符(无论是用逗号还是分号隔开都是不推荐的),一行一个就可以了。这样你就可以在每一行对每一个标识符进行解释。

第九章:内存分配 

内核提供了下列通用内存分配器:kmalloc()、kzalloc()、kcalloc()、vmalloc()、和 vzalloc()。 更多信息,请参阅的 API 文档。

传递一个结构体大小的最好方式如下:

C语言

另外一种传递方式中,sizeof的操作数是结构体的名字,这样有损可读性,并会在指针类型改变,但传递给内存分配器的大小没有变化时导致BUG。 强制转换返回的void指针是冗余的。C语言本身保证了从void指针到其他任何指针类型的转换。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分