邪恶八进制信息安全团队技术讨论组's Archiver

代码罐头 2006-1-25 13:24

[转载]C程序代码风格

原始连接:[url]http://www.ecnweb.net/modules.php?name=Forums&file=viewforum&f=4[/url]


下面这篇文章是linux内核中Documentation/CodingStyle文件,觉得挺有意思,就顺手把它译出来了,因为虽然这只是“linux”的代码风格,但优秀的C程序风格大致无二。特别是emacs相关的东西,肯定有误译,请多指正



代码:
Linux内核编码风格

这篇简短的文章描述了Linux内核首选的编码风格。编码风格是很个人化的东西,我不会把自己的观点强加给任何人。但是,Linux内核的代码毕竟是我必须有能力维护的,因此我宁愿它的编码风格是我喜欢的。请至少考虑一下这一点。
首先,我建议打印一份《GNU编码标准》,不要阅读它。烧掉它,它不过是象征性的姿态。
然后,请看:


第 1 章: 缩进

Tabs(制表符)是8个字符的大小,因此缩进也应该是8个字符的大小。有些叛逆主张试图把缩进变成4个(甚至是2个!)字符的长度,这就好象试图把PI(案,圆周率)定义成3是一样的。
依据:缩进背后的思想是:清楚地定义一个控制块从哪里开始,到哪里结束。尤其是在你连续不断的盯了20个小时的屏幕后,如果你有大尺寸的缩进。你将更容易发现缩进的好处。
现在,有些人说8个字符大小的缩进导致代码太偏右了,并且在一个80字符宽的终端屏幕上看着很不舒服。对这个问题的回答是:如果你有超过3个级别的缩进,你就有点犯糊涂了,应当修改你的程序。
简而言之,8个字符的缩进使程序更易读,而且当你把功能隐藏的太深时,多层次的缩进还会对此很直观的给出警告。要留心这种警告信息。


第 2 章: 放置花括号

C程序中另一个要主意的就是花括号的放置。与缩进尺寸不同的是,关于如何放置花括号没有技术上的理由。但是,首选的方法是象先知Brain Kernighan和Dennis Ritchie展现的那样:把左括号放在行尾,右括号放在行首。也就是:

if (x is true) {
we do y
}

然而,还有另外一种情况,就是函数:函数应当把左右括号都放在行首。也就是:

int function(int x)
{
body of function
}

叛逆的人们所在皆有。他们说,这样会导致…嗯,不一致性(案,指函数的花括号使用与其他情况不统一)。但是所有正确思考的人都知道:(1) K&R是正确的;(2) K&R还是正确的。 而且,函数与别任何东西都不一样(在C语言中你没法隐藏它)。
注意,右括号所在的行不应当有其它东西,除非跟随着一个条件判断。也就是do-while语句中的“while”和if-else语句中的“else”。象这样:

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

和:

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

依据: K&R。
而且,注意这种花括号的放置减少了空行的数目,并没损害可读性。因此,当屏幕上不可以有很多空行时(试想25行的终端屏幕),你就有更多的空行来安插注释。


第 3 章: 命名

C是一门朴素的语言,你使用的命名也应该这样。与Modula-2和Pascal程序员不同,C程序员不使用诸如 “ThisVariableIsATemporaryCounter”这样“聪明”的名字。C程序员应该叫它“tmp”,这写起来更简单,也不会更难懂。
然而,当面对复杂情况时就有些棘手,给全局变量取一个描述性的名字是必要的。把一个全局函数叫做“foo”是一种目光短浅的行为。
全局变量(只当你确实需要时才用)应该有描述性的名字,全局函数也一样。如果你有一个统计当前用户个数的函数,应当把它命名为“count_active_user()”或者简单点些的类似名称,不应该命名为“cntusr()”。
把函数类型写进函数名(即所谓的“匈牙利命名法”)简直就是大脑有问题──编译器总是知道函数的类型并且能加以检查,这种命名法只会弄糊涂程序员自己。怪不得微软总是制造充满bug的程序。
局部变量的名字应该尽量短,而且说到点子上。如果你有个普通的整型循环计数变量,应当命名为“i”。命名为“loop_counter”并不能带来任何成效,如果它不被误解的话(案,这里的言外之意是说,如果被误解就更惨了)。与此类似,“tmp”可以作为一个用来存储任何类型临时值的变量的名字。
如果你害怕弄混淆局部变量(s)的名字,你就面临着另一个问题,也叫作“函数增长荷尔蒙失调综合症”。请参考下一章。


第 4 章: 函数

函数应当短而精美,而且只做一件事。它们应当占满1或2个屏幕(就象我们知道的那样,ISO/ANSI的屏幕大小是80X24),只做一件事并且把它做好。
一个函数的最大长度与它的复杂度和缩进级别成反比。所以,如果如果你有一个概念上简单(案,“简单”是simple而不是easy)的函数,它恰恰包含着一个很长的case语句,这样你不得不为不同的情况准备不懂的处理,那么这样的长函数是没问题的。
然而,如果你有一个复杂的函数,你猜想一个并非天才的高一学生可能看不懂得这个函数,你就应当努力把它减缩得更接近前面提到的最大函数长度限制。可以使用一些辅助函数,给它们取描述性的名字(如果你认为这些辅助函数的调用是性能关键的,可以让编译器把它们内联进来,这比在单个函数内完成所有的事情通常要好些)。
对函数还存在另一个测量标准:局部变量的数目。这不该超过5到10个,否则你可能会弄错。应当重新考虑这个函数,把它分解成小片。人类的大脑一般能同时记住7个不同的东西,超过这个数目就会犯糊涂。或许你认为自己很聪明,那么请你理解一下从现在开始的2周时间你都做什么了。


第 5 章:注释

注释是有用的,但过量的注释则是有害的。不要试图在注释中解释你的代码是如何工作的:把代码是如何工作的视为一件显然的事情会更好些,而且,给糟糕的代码作注释则是在浪费时间。

通常,你愿意自己的注释说出代码是做什么的,而不是如何做。还有,尽量避免在函数体内作注释:如果函数很复杂,你很可能需要分开来注释,回头到第4章去看看吧。你可以给一段代码──漂亮的或丑陋的──作注释以引起注意或警告,但是不要过量。取而代之,应当把注释放在函数首部,告诉人们该函数作什么,而不是为什么这样做。


第 6 章:你把事情弄乱了

好吧,我们来看看。很可能有长期使用UNIX的人告诉过你,“GNU emacs”能自动为你格式化C程序源代码,你注意到这是真的,它确实能做到,但是缺省情况下它的用处远远小于期望值──键入无数的monkeys到GNU emacs中绝不可能造出好的程序。
因此,你可以或者删除GNU emacs,或者对它进行理智的配置。对于后者,可以把下面的行粘贴到你的.emacs文件中:

(defun linux-c-mode ()
"C mode with adjusted defaults for use with the Linux kernel."
(interactive)
(c-mode)
(c-set-style "K&R")
(setq c-basic-offset Cool)

这将会定义一个把C代码弄成linux风格的命令。当hacking一个模块时,如果你把“-*- linux-c -*-”放到了最初的两行,这个模块将被自动调用。而且,如果你打算每当在/usr/src/linux下编辑源文件时就自动调用它,也许你会把下面的命令:
(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\\.[ch]$" . linux-c-mode)
auto-mode-alist))
添加进你的.emacs文件。
但是,即使你没能让emacs正确做到格式化,也并非将就此一无所有:还有“indent”程序呢。
嗯,再提醒一下,GNU indent跟GNU emacs有同样的毛病,这就需要你给它一些命令行选项。然而,这不是很糟糕的事,因为即使是GNU indent也承认K&R的权威性(GNU的人不是魔鬼,他们只是在这里太过严格了,以致于误导人),所以你可以只需给indent这样的选项: “-kr -i8”(表示“K&R风格,8个字符的缩进”)。
“indent”程序有很多选项,特别是当为重排过的程序作注释的时候,你需要看一下它的手册。记住:“indent”可不是修正糟糕程序的万能钥匙。


第 7 章: 配置文件(configuration-files)

对配置选项来说(arch/xxx/config.in和所有的Config.in文件),使用不同的缩进风格。
若代码中的缩进级别为3,配置选项就应该为2,这样可以暗示出依赖关系。后者只是用于bool/tristate(即二态/三态)的选项。对其它情况用常识就行了。举例来说:

if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
tristate 'Apply nitroglycerine inside the keyboard (DANGEROUS)' CONFIG_BOOM
if [ "$CONFIG_BOOM" != "n" ]; then
bool ' Output nice messages when you explode' CONFIG_CHEER
fi
fi

通常CONFIG_EXPERIMENTAL应当在所有不稳定的选项的周围出现。所有已知会破坏数据的选项(如文件系统的实验性的写支持功能)应当被标记为(DANGEROUS),其他实验性的选项应当被标记为(EXPERIMENTAL)。


第 8 章: 数据结构

假如数据结构在其被创建/销毁的线程环境(案:这里说的线程是一个执行实体,可能是进程、内核线程或其它)之外还具有可见性,那么他们都该有引用计数。在内核中没有垃圾收集机制(而且内核之外的垃圾收集也是缓慢而低效的),这意味着你绝对应该为每一次使用进行引用计数。
引用计数意味着你可以避开锁,还能允许多个线程并行访问该数据结构──而且不用担心仅仅因为访问数据结构的线程睡眠了一会儿或者干别的去了,它们就会消失。
注意,锁不是引用计数的替代品。锁是用来保持数据结构的一致性的,而引用计数是一种内存管理技术。通常二者都需要,而且不会彼此混淆。
确实有许多数据结构可以有两个级别的引用计数,当使用者具有不同的“等级”(classes)时就是这样。子等级(subclass)记录了处于该等级的使用者个数,而且当它减到零的时候就把总体计数(global count)减一。
这种“多级引用计数”(multi-reference-counting)的一个实例可以在内存管理子系统("struct mm_struct":mm_users和mm_count)中找到,也可以在文件系统的代码中("struct super_block":s_count和s_active)找到。
记住:如果另一个线程能找到你的数据结构,而你有没对它做引用计数,那几乎可以肯定:这是一个bug。

代码罐头 2006-1-25 13:38

我看到另外一篇翻译稿
两者都有不是翻译的特别好的地方,这里贴出来以供参考


这篇短小的文档用于描述linux内核编程中推荐的编程风格。编程风格是很个人
化的东西,我不想把我的观点_强加_给任何人,但这是我必须维护的代码中所遵守
的,我也建议其他部分的代码也能遵守它。请至少给这里的观点一些考虑。

首先,我建议你打印一份GNU代码风格,不是去读它,而是把它烧了,这是个很
不错的姿态。
不废话了,下面就是Linux内核编程风格:

第一章:缩进

制表符(tabs)占8个字符,所以缩进也是8个字符。有些异端运动想使用4个字符
(甚至是2个字符)的缩进,这和把PI(圆周率)定为3没什么两样。

原因:缩进的根本目的是用来清晰地标识一个控制块的起始。特别是当你连续盯
着屏幕看了20 个小时后,你就会体会到更长的缩进的好处了。

现在,有些人提出8字符缩进会使得代码太偏向右边,当使用80字符的终端
时很难阅读。答案是如果你需要三层以上的缩进,那么你已经完蛋了,应该改改
你的程序了。

简而言之,8字符缩进使得阅读代码更为容易,并且在你的缩进层次过深时提出
警告。应该留心这样的警告。
关于这一点,我不能苟同,4个字符的缩进确实有它的好处,缩进太多反而看着累。
而且有时候仅仅两层嵌套就会使代码很长。


第二章:括号的位置

括号位置的问题在C编程风格中经常被提出。和缩进大小不同,括号位置的选择
并没有太多技术上的原因,而更多的是个人的喜好。比如Kernighan和Ritchie的
弟子们把左括号放在一行的最后,把右括号放在一行的开始,象这样:

if (x is true) {
we do y
}


但是,函数是一种特殊的情况,函数的左括号放在下一行的开始,象这样:
int function(int x)
{
body of function
}
全世界的异端人士指出这种不一致的做法 ...嗯... 不太一致,但是所有思维正
确的人知道 (a) K&R是_对_的 (b) K&R是对的。而且,函数确实是特殊的(你在C
中无法对函数进行嵌套)。

注意到右括号完全占有单独的一行,_除非_当它后面还有未完成的语句,比如do
语句中的“while”或者if语句中的“else”,想这样:

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



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

原因:K&R。

还有,注意到这种括号的布局方法还减少了空行(或者说是几乎是空行)的数目,
而且没有减小可读性。因为你屏幕上的空行是不可回收资源(这里想一下25行的
终端屏幕),这样你会有更多的空行用于加注释。


第三章:命名

C是个斯巴达式(崇尚简洁风格的)语言,所以你的命名方法也应该如此。与
Modula-2和Pascal程序员不同,C程序员不使用
ThisVariableIsATemporaryCounter这样可爱的名字。一个C程序员会把一个变量
叫做“tmp”,这样的变量名更容易写,而且理解起来也不算太难。

但是_,尽管人人都会对大小写混杂的名字皱眉头,全局变量名则必须如此。管
一个全局函数叫“foo”是故意找岔。

_全局_变量(只有在_真正_需要时才使用)需要有个描述性强的名字,这点和全
局函数一样。如果你有个函数用于对活跃用户进行计数,你应该叫它
“count_active_users()”,而不是“cntusr()”。

把函数的类型加入到名字中(所谓的匈牙利命名法)是脑损伤的表现 - 编译器
知道类型,能够对它进行检查,这种命名法只会把程序员自己搞晕。难怪微软做
了那么多充满bug的程序。 哈哈^_^,有道理,不要作茧自缚

_局部_变量应该短小扼要。如果你有个随机的整数循环变量,可能最好叫它“i”。
把它叫做“loop_counter”是效率低下的,在不会发生混淆的情况下。类型地,
“tmp”可以被用于任何类型的存储临时值的变量。

如果你担心混淆你的局部变量,那么你就会有另一个问题,所谓的函数膨胀荷尔
蒙失衡综合症,请看下一章。


第四章:函数

函数应该短小而甜美,而且只能做一件事。他们应该只用一两屏幕(我们都知道,
ISO/ANSI标准屏幕大小是80x24)就能装下,只做并且做好一件事。

函数的最大长度应该与函数的复杂性和缩进层次成反比。所以,如果你有个只有
一个很长(但很简单)的case语句的函数,对许多case做一些很少的操作,那么
这个函数长点也没有关系。

但是,如果你有一个复杂的函数,你担心一个中等智力的高一学生可能无法理解,
那么你应该更严格地遵守最大长度限制。使用有描述性名字的帮助函数(你可以
让编译器in-line这些帮助函数,如果你认为性能很重要的话,而且编译器恐怕
会比你做的要好)。

函数的另一个指标是局部变量的数目,局部变量的数目不应超过5-10个,否则一
定是哪里有问题了。再设计一下这个函数,把它分解得更小一些。人的大脑一般
可以同时跟踪7个不同的东西,超过了7个就会晕菜。虽然你很聪明,不过可能你
有时会想理解一下两星期前所写的代码。


第五章:注释

注释是好东西,不过存在过分注释的危险。_永远_不要在注释中解释你的代码是
如何工作的:更好的做法是写出工作方式显而易见的代码,解释糟糕的代码是浪
费时间。

一般来说,注释应该说明代码在做什么,而不是怎么做。并且,不要把注释加在
函数主体中:如果函数太复杂以至于必须对各个部分进行注释,那么你可能要再
去读读第四章。你可以加入一些短小的注释来提醒或警告一些聪明(或难看)的
做法,但不要太过度。更好的选择是,把注释放在函数头,说明函数在做什么,
可能还包括它为什么做。


第六章:你的代码乱七八糟

没什么,我们都遇到过。你可能从老Unix用户那里听说过“GNU emacs”会自动
对齐C源代码,但缺省的设置不是很好(事实上,缺省设置比胡乱敲打还糟糕 -
一群使用GNU emacs猴子永远不会做出漂亮的程序)。
靠!我就是用emacs的!不过现在自动对齐还算可以了。

所以,你或者彻底仍掉GNU emacs,或者采用更理智的设置。如果选择后者,你
可一把下面的代码加到你的.emacs文件中:

(defun linux-c-mode ()
"C mode with adjusted defaults for use with the Linux kernel."
(interactive)
(c-mode)
(c-set-style "K&R")
(setq c-basic-offset 8))

这会定义 M-x linux-c-mode 命令。当编写Linux模块时,如果你把字符串“-*-
linux-c -*-”放在文件的头两行中,这个模式就会被自动激活。还有,如果你
想在编辑/usr/src/linux目录下的源文件时linux-c-mode被自动激活,你在你的.
emacs文件中需要加入

(setq auto-mode-alist (cons '("/usr/src/linux.*/.*.[ch]$" . linux-c-mode)
auto-mode-alist))

但是即使你用不了emacs,并不是世界末日:你还可以使用“indent”。

又一次,GNU indent使用了和GNU emacs一样的脑死亡设置,所以你需要给它一
些命令行选项。但是,这不算太坏,因为即使是GNU indent的作者们也意识到了
K&R的权威性(GNU的人也不是魔鬼,他们只是在这件事上被误导了),所以你可
以使用选项“-kr -i8”(表示“K&R,8字符缩进”)运行indent。

“indent”有很多选项,特别是注释布局部分,你可能想看看它的man手册。但
是请记住:“indent”不能修改糟糕的程序。
个人觉得现在emacs对缩进对齐的处理已经不错了,问题在于你使用不同的Linux
发行版,就会面对不同的.emacs文件,有时候我不得不随身携带我的.emacs文件,
或者把它放在我的email的存储箱里面


第七章:配置文件

配置选项 (arch/xxx/config.in,以及所有Config.in文件)使用了有些不同的
缩进方式。

代码中使用的是3字符缩进,config-选项中应该使用2字符缩进标识依赖关系。
后者只应用于bool/tristat选项。对于其他选项,采用你认为最合适的缩进方式
就可以了。例如:

if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
tristate 'Apply nitroglycerine inside the keyboard (DANGEROUS)' CONFIG_BOOM
if [ "$CONFIG_BOOM" != "n" ]; then
bool ' Output nice messages when you explode' CONFIG_CHEER
fi
fi

一般来说,所有不稳定的选项应该标为CONFIG_EXPERIMENTAL。所有可能损坏数
据的的选项应该标为(DANGEROUS),其他的试验选项应该标为(EXPERIMENTAL)。


第八章:数据结构

供多线程使用的数据结构应该采用引用计数(reference counts)。在内核中,
垃圾回收(garbage collection)是不存在的(内核之外的垃圾回收效率不高),
这意味着你_必须_使用引用计数。

引用计数的使用能避免锁的使用,使不同的用户能够并行使用数据结构 - 不需
要担心结构会因为睡眠而突然消失。

注意到加锁_不是_引用计数的替代物。加锁用于保证数据结构的完整性,而引用
计数是一个内存管理技术。通常你两个都需要,不应该有任何混淆不清的地方。

一些数据结构可能使用两层的引用计数,当对不同的“类”都有使用的时候。子
类的计数统计所有子类用户的数目,当子类的计数为零时只对总计数减一。

这种“多层引用计数”的例子可以在内存管理代码(“struct mm_struct”:
mm_users和mm_cout)和文件系统代码(“struct super_block”:s_count和
s_active)中找到。

记住:如果另一个线程能够看见你的数据结构,而你却没有对它使用引用计数,
那么几乎可以肯定会有bug存在。

代码罐头 2006-1-26 12:58

不同写法不会影响编译器对源代码的解释.
但是对于今后维护你的代码的其他人甚至你自己来说,
保持统一的变成风格是非常有用的.

evilknight 2006-1-26 20:47

第一章:缩进

制表符(tabs)占8个字符,所以缩进也是8个字符。

我认为二个字符就好了,符合中国人的书写习惯。

页: [1]
© 1999-2008 EvilOctal Security Team