加入收藏 | 设为首页 | 会员中心 | 我要投稿 拼字网 - 核心网 (https://www.hexinwang.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

「如何像傻瓜一样写C语言」Linux内核编程规范

发布时间:2022-09-21 15:22:48 所属栏目:Linux 来源:
导读:  今天是风和日丽的周一,一周中最想摸鱼的日子。机缘巧合下浏览了Linux kernel coding style,发现一些对我有所启发的内容。一句话:如果你选择遵循以下的规则,你会发现C语言本身变得更加简单好懂(当然,代价是
  今天是风和日丽的周一,一周中最想摸鱼的日子。机缘巧合下浏览了Linux kernel coding style,发现一些对我有所启发的内容。一句话:如果你选择遵循以下的规则,你会发现C语言本身变得更加简单好懂(当然,代价是不再「强大」),从而可以把更多的精力集中在你需要编写的复杂软件上面,比如内核。原文并不长,我并不打算逐字翻译,并且省略了内核开发特定的部分,以节省不需要开发内核的读者的时间。欢迎感兴趣的各位去读一下原文。
 
  以下内容当中,斜体内容和引用块为原文翻译(引用块当中的嘲讽浓度较高),其中粗体是我认为对我帮助比较大的,其它内容是我结合原文的讨论和自己的理解总结出来的。我并不完全同意文中的每一句话,但是整体上来说高度认可。
 
  使用8格缩进。作者认为应该使用tab,但是出于平台兼容性的考虑我觉得还是用空格比较合适一点。作者强烈反对将缩进改为4格甚至2格,认为任何超过三层缩进的代码逻辑都已经完蛋了。对于C语言来说这大体上来说是对的。在一些特殊的场合下(比如switch-case语句),避免重复缩进,因为没必要。
 
  不要超过80宽度,超过了要换行。例外:展示给用户的字符串应当保持原样,否则可能影响使用grep的结果。关于80宽度是不是已经太保守了这一点一直都是争论的焦点,我的看法是对于C语言(并且遵守这个规范),80宽度作为非强制要求是合理的。
 
  对左花括号的放置遵循K&R规范,即不换行,除非是函数体。对于可以一行写完的缩进块linux语句,可以省略花括号,但是如果是if的一个分支并且另外一个分支一行写不完就不要省略。这一部分的考量主要是出于节省屏幕空间。
 
  全世界的异教徒们声称这种(对函数体和其他缩进块)不一致是……呃……不一致的。但是任何头脑正常(right-thinking)的人都知道(a) K&R are right and (b) K&R are right.
 
  这里到底是什么意思各位自行理解。
 
  在语言关键字之后应该留一个空格,但是那些用起来像函数的(sizeof,typeof,alignof,__attribute__)不要留,并且虽然语言没有要求但应该给它们的参数加括号。不要在括号的内侧留空格。声明指针的星号应该靠近变量名而不是类型。不得不说这一组规范真是深得我心。
 
  局部名字应该短小精悍,而全局可见的名字(全局变量和函数名)应该写完整。不要用匈牙利命名法。
 
  难怪微软搞的软件全是bug。
 
  不要用master/slave或者blacklist/whitelist作为变量名。推荐的替换列表见原文。
 
  不要滥用typedef。很多人认为typedef可以提升代码可读性,有利无害。但是其实只有在下列情况它是有用的,并且只应该在这些情况下使用:
 
  对于这一条规范,我以前其实并没有遵循,而是按照写OO的习惯尽可能地把结构体写成隐藏内部的形式。结合新的语言(如Rust)的设计我觉得它说的有道理。另外typedef本身似乎并不能强迫编译器拒绝最终类型一致的参数,想要起到限制参数类型的目的应该还要一些额外的工作。
 
  函数的长度不要超过两屏幕(48行)。这个评判标准并不是为了读起来方便本身,而是作为函数实现复杂程度的一种近似估计。
 
  如果你发现那些并非天赋异禀的高中一年级生甚至搞不懂这函数是干嘛的,那你就应该更严格地执行最大长度限制了。
 
  函数的局部变量不应该超过5-10个。
 
  人类的大脑通常能跟踪7个不同的东西。你知道你聪明得很,但你还是想理解你两周以前干了点啥。
 
  在定义函数原型的时候,尽管语言没有要求,但还是把参数名字写上。并且不要用extern,没用而且增加代码长度。
 
  使用goto模拟try-catch-finally。 通常大家都说永远别用goto,但是对于错误处理当中的使用我觉得确实有必要,因为不用的话太容易写错了。通过写一些宏定义来隐藏对goto的使用(并且加强可读性)我觉得应该是一个不错的折中。
 
  要写注释,并且不要写没用的注释。注释永远不要讲一件事是怎么(HOW)办到的,如果你需要写这种注释那说明你很有可能写了一些烂代码。代码为什么能工作应该是一目了然的。注释只应该写代码做的事情本身是什么(WHAT)。另外,注释一般不应该出现在函数体内部,不然这个函数可能太长了需要进一步拆分。把注释放在函数开头,告诉人们它干了什么,也许包括它为什么(WHY)要这么干。想要区分为什么做一件事和怎么做一件事是需要一定的领悟的。
 
  多行长注释应该在每一行开头加一个星号。
 
  定义常量的宏和枚举常量应该全大写命名。所以说,函数形式的宏不应该全大写。
 
  多行的宏应该用do { ... } while (0)包裹。这算是常识级别的冷知识吧,不理解的人可以自己去查查。
 
  不要用宏改变控制流,比如return。不要在宏里引用魔法局部变量名。记得给带有运算符的常量加括号。对于常用的变量名,做一些名字混淆减小冲突的可能性。
 
  打印信息的时候拼写的正(zhuang)式(bi)一点。不要写dont,写don't或者do not。句尾不需要加句号。打印整数的时候不要加括号,没什么用。尽量用最合适的函数打印输出。当然在非内核环境下除了printf也没有太多可以选择的函数了,但是搭配一些日志库的时候可以这样做。
 
  调用malloc动态分配内存的时候sizeof的参数用变量名而不是类型。这样如果以后变量的类型变了可以少改一点,并且可读性会更好。
 
  不要到处用inline。如果代码段太臃肿了,占用了太多的高速缓存,程序的运行效率也不会好。对于只用了一次的函数,编译器可以自动内联。所以总而言之就别inline了。
 
  统一函数的返回值。有两种常用的规范:成功返回0,出错返回错误码,或者成功返回1,错误返回0。这两种规范混着用是非常灾难性的,但是可惜事实就是这样。所以一个建议是:对于名字像是命令的函数(如add_work),采用成功返回0;对于名字像是判断的函数(如pci_dev_present),采用失败返回0。个人觉得这个方案很不错,当然文档还是要写好的。
 
  尽量用C99的_Bool类型,编译器可以做更多检查。
 
  不要在源代码里包含编辑器配置(modelines)。我个人不认同这一点,并且支持通过editorconfig之类的方案来统一所有开发者格式化工具的行为。这样可以减少配置环境的手续,并且避免格式化工具引入无意义的diff。
 
  不要在.c文件中引入条件编译,而是在.h中。这里实际上说的是条件编译不要发生在调用方而是发生在实现方,这有利于模块化,是很好的实践。
 
  本文全文如上。祝各位新的一个月活干得完。
 

(编辑:拼字网 - 核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!