Recommended C Style and Coding Standards中文翻译版第1/3页_C 语言

0. 摘要

本文翻译自《Recommended C Style and Coding Standards》。

作者信息:

L.W. Cannon (Bell Labs)
R.A. Elliott (Bell Labs)
L.W. Kirchhoff (Bell Labs)
J.H. Miller (Bell Labs)
J.M. Milner (Bell Labs)
R.W. Mitze (Bell Labs)
E.P. Schan (Bell Labs)
N.O. Whittington (Bell Labs)
Henry Spencer (Zoology Computer Systems, University of Toronto)
David Keppel (EECS, UC Berkeley, CS&E, University of Washington)
Mark Brader (SoftQuad? Incorporated, Toronto)

本文是《Indian Hill C Style and Coding Standards》的更新版本,上面提到的最后三位作者对其进行了修改。本文主要介绍了一种C程序的推荐编码标准,内容着重于讲述编码风格,而不是功能 组织(Functional Organization)。

1. 简介

本文档修改于AT&T Indian Hill实验室内部成立的一个委员会的一份文档,旨在于建立一套通用的编码标准并推荐给Indian Hill社区。

本文主要讲述编码风格。良好的风格能够鼓励大家形成一致的代码布局,提高代码可移植性并且减少错误数量。

本文不关注功能组织,或是一些诸如如何使用goto的一般话题。我们尝试将之前的有关C代码风格的文档整合到一套统一的标准中,这套标准将适合于 任何使用C语言的工程,当然还是会有部分内容是针对一些特定系统的。另外不可避免地是这些标准仍然无法覆盖到所有情况。经验以及广泛的评价十分重 要,遇到特殊情况时,大家应该咨询有经验的C程序员,或者查看那些经验丰富的C程序员们的代码(最好遵循这些规则)。

本文中的标准本身并不是必需的,但个别机构或团体可能部分或全部采用该标准作为程序验收的一部分。因此,在你的机构中其他人很可能以一种相似的风 格编码。最终,这些标准的目的是提高可移植性,减少维护工作,尤其是提高代码的清晰度。

这里很多风格的选择都有些许武断。混合的编码风格比糟糕的编码风格更难于维护,所以当变更现有代码时,最好是保持与现有代码风格一致,而不是盲目 地遵循本文档中的规则。

"清晰的是专业的;不清晰的则是外行的" — Sir Ernest Gowers

2. 文件组织

一个文件包含的各个部分应该用若干个空行分隔。虽然对源文件没有最大长度限制,但超过1000行的文件处理起来非常不方便。编辑器很可能没有足够 的临时空间来编辑这个文件,编译过程也会因此变得十分缓慢。与回滚到前面所花费的时间相比,那些仅仅呈现了极少量信息的多行星号是不值得的,我们 不鼓励使用。超过79列的行无法被所有的终端都很好地处理,应该尽可能的避免使用。过长的行会导致过深的缩进,这常常是一种代码组织不善的症状。

2.1 文件命名惯例

文件名由一个基础名、一个可选的句号以及后缀组成。名字的第一个字符应该是一个字母,并且所有字符(除了句号)都应该是小写的字母和数字。基础名 应该由八个或更少的字符组成,后缀应该由三个或更少的字符组成(四个,如果你包含句号的话)。这些规则对程序文件以及程序使用和产生的默认文件都 适用(例如,"rogue.sav")。

一些编译器和工具要求文件名符合特定的后缀命名约定。下面是后缀命名要求:

    C源文件的名字必须以.c结尾
    汇编源文件的名字必须以.s结尾

我们普遍遵循以下命名约定:

    可重定位目标文件名以.o结尾
    头文件名以.h结尾
        在多语言环境中一个可供选择的更好的约定是用语言类型和.h共同作为后缀(例如,"foo.c.h" 或 "foo.ch")。

    Yacc源文件名以.y结尾
    Lex源文件名以.l结尾

C++使用编译器相关的后缀约定,包括.c,..c,.cc,.c.c以及.C。由于大多C代码也是C++代码,因此这里并没有一个明确的方案。

此外,我们一般约定使用"Makefile"(而不是"makefile")作为make(对于那些支持make的系统)工具的控制文件,并且使 用"README"作为简要描述目录内容或目录树的文件。

2.2 程序文件

下面是一个程序文件各个组成部分的推荐排列顺序:

文件的第一部分是一个序,用于说明该文件中的内容是什么。对文件中的对象(无论它们是函数,外部数据声明或定义,或是其他一些东西)用途的描述比 一个对象名字列表更加有用。这个序可选择地包含作者信息、修订控制信息以及参考资料等。

接下来是所有被包含的头文件。如果某个头文件被包含的理由不是那么显而易见,我们需要通过增加注释说明原因。大多数情况下,类似stdio.h这 样的系统头文件应该被放在用户自定义头文件的前面。

接下来是那些用于该文件的defines和typedefs。一个常规的顺序是先写常量宏、再写函数宏,最后是typedefs和枚举 (enums)定义。

接下来是全局(外部)数据声明,通常的顺序如下:外部变量,非静态(non-static)全局变量,静态全局变量。如果一组定义被用于部分特定 全局数据(如一个标志字),那么这些定义应该被放在对应数据声明后或嵌入到结构体声明中,并将这些定义缩进到其应用的声明的第一个关键字的下一个 层次(译注:实在没有搞懂后面这句的含义)。

最后是函数,函数应该以一种有意义的顺序排列。相似的函数应该放在一起。与深度优先(函数定义尽可能在他们的调用者前后)相比,我们应该首选广度 优先方法(抽象层次相似的函数放在一起)。这里需要相当多的判断。如果定义大量本质上无关的工具函数,可考虑按字母表顺序排列。

2.3 头文件

头文件是那些在编译之前由C预处理器包含在其他文件中的文件。诸如stdio.h的一些头文件被定义在系统级别,所有使用标准I/O库的程序必须 包含它们。头文件还用来包含数据声明和定义,这些数据不止一个程序需要。头文件应该按照功能组织,例如,独立子系统的声明应该放到独立的头文件 中。如果一组声明在代码从一种机器移植到另外一种机器时变动的可能性很大,那么这些声明也应该被放在独立的头文件中。

避免私有头文件的名字与标准库头文件的名字一样。下面语句:

复制代码 代码如下:

#include "math.h"

当预期的头文件在当前目录下没有找到时,它将会包含标准库中的math头文件。如果这的确是你所期望发生的,那么请加上注释。包含头文件时不要使 用绝对路径。当从标准位置获取头文件时,请使用<name>包含头文件;或相对于当前路径定义它们。C编译器的"include- path"选项(在许多系统中为-l)是处理扩展私有库头文件的最好方法,它允许在不改变源码文件的情况下重新组织目录结构。

声明了函数或外部变量的头文件应该被那些定义了这些函数和变量的文件所包含。这样一来,编译器就可以做类型检查了,并且外部声明将总是与定义保持 一致。

在头文件中定义变量往往是个糟糕的想法,它经常是一个在文件间对代码进行低劣划分的症状。此外,在一次编译中,像typedef和经过初始化的数 据定义无法被编译器看到两次。在一些系统中,重复的没有使用extern关键字修饰的未初始化定义也会导致问题。当头文件嵌套时,会出现重复的声 明,这将导致编译失败。

头文件不应该嵌套。一个头文件的序应该描述其使用的其他被包含的头文件的实用特性。在极特殊情况下,当大量头文件需要被包含在多个不同的源文件中 时,可以被接受的做法是将公共的头文件包含在一个单独的头文件中。

一个通用的做法是将下面这段代码加入到每个头文件中以防止头文件被意外多次包含。

复制代码 代码如下:

#ifndef EXAMPLE_H
#define EXAMPLE_H
 …    /* body of example.h file */
#endif /* EXAMPLE_H */

我们不应该对这种避免多次包含的机制产生依赖,特别是不应该因此而嵌套包含头文件。

2.4 其他文件

还有一个惯例就是编写一个名为"README"的文件,用于描述程序的整体情况以及问题。例如,我们经常在README包含程序所使用的条件编译 选项列表以及相关说明,还可以包含机器无关的文件列表等。

3. 注释

"当代码与注释不一致时,两者很可能都是错的" -- Norm Schryer

注释应该描述发生了什么,如何做的,参数的含义,使用和修改了哪些全局变量以及约束或Bugs。避免给那些本身很清晰的代码加注释,因为这些注释信息将很快的过时。注释与代码不一致将会带来负面影响。短小的注释应该是关于做什么的,比如"计算有意义的值",而不是关于"怎么做"的,例如"值的总和除以n"。C不是汇编;在头3-10行的区域添加注释,说明代码总体是做什么的,经常要比为每行添加注释说明微逻辑更加有用。

注释应该为那些令人不悦的代码作出"辩护"。辩护应该是这样的:如果使用正常的代码,一些糟糕的事情将会发生。但仅仅让代码运行的更快还不足以让这些hack代码显得合理化;而是应该将那些在不使用hack代码时令人无法接受的性能数据展示出来。注释应该对着写不可接受的行为作出解释,并告诉大家为什么使用Hack代码可以很好的解决这个问题。

那些用于描述数据结构,算法等的注释应该以块注释的形式存在。块注释起始以/*占据1-2列,*放在每行注释前面的第二列,块注释最后以占据2-3列的*/结尾。另外一个候选方案是每行注释文字前面用*占据1-2列,块注释以占据1-2列的*/收尾。

复制代码 代码如下:

/*
 *    Here is a block comment.
 *    The comment text should be tabbed or spaced over uniformly.
 *    The opening slash-star and closing star-slash are alone on a line.
 */

/*
** Alternate format for block comments
*/

注意

复制代码 代码如下:

grep '^.\e*'

将匹配文件中所有的注释。特别长的块注释,诸如持久讨论或版权声明,经常以占据1-2列的/*开始,每行注释文字前没有*,并最终以占据1-2列的*/结束。函数内部很适合使用块注释,块注释应该与其描述的代码拥有相同的缩进。独立的单行注释也应该与其说明的代码缩进一致。

复制代码 代码如下:

if (argc > 1) {
    /* Get input file from command line. */
    if (freopen(argv[1], "r", stdin) == NULL) {
        perror (argv[1]);
    }
}

特别短的注释可以与其描述的代码放在同一行上,并且要通过tab与代码语句分隔开来。如果针对一块代码有不止一个短注释,这些注释应该具有相同的缩进。

复制代码 代码如下:

if (a == EXCEPTION) {
    b = TRUE;                /* special case */
} else {
    b = isprime(a);            /* works only for odd a */
}

4. 声明

全局声明应该从第一列开始。在所有外部数据声明的前面都应该放置extern关键字。如果一个外部变量是一个在定义时大小确定的数组,那么这个数 组界限必须在extern声明时显示指出,除非数组的大小与数组本身编码在一起了(例如,一个总是以0结尾的只读字符数组)。重复声明数组大小对 于一些使用他人编写的代码的人特别有益。

指针修饰符*应该与变量名在一起,而不是与类型在一起。

复制代码 代码如下:

char  *s, *t, *u;

替换

复制代码 代码如下:

char*  s, t, u;

后者是错误的,因为实际上t和u并未如预期那样被声明为指针。

不相关的声明,即使是相同类型的,也应该独立占据一行。我们应该对声明对象的角色进行注释,不过当常量名本身足以说明角色时,使用#define 定义的常量列表则不需要注释。通常多行变量名、值与注释使用相同缩进,使得他们在一列直线上。尽量使用Tab字符而不是空格。结构体和联合体的声 明时,每个元素应该单独占据一行,并附带一条注释。{应该与结构体的tag名放在同一行,}应该放在声明结尾的第一列。

复制代码 代码如下:

struct boat {
    int    wllength;    /* water line length in meters */
    int    type;        /* see below */
    long   sailarea;    /* sail area in square mm */
};

/* defines for boat.type */
#define    KETCH    (1)
#define    YAWL     (2)
#define    SLOOP    (3)
#define    SQRIG    (4)
#define    MOTOR    (5)

这些defines有时放在结构体内type声明的后面,并使用足够的tab缩进到结构体成员成员的下一级。如果这些实际值不那么重要的话,使用 enum会更好。

复制代码 代码如下:

enum bt { KETCH=1, YAWL, SLOOP, SQRIG, MOTOR };
struct boat {
    int     wllength;    /* water line length in meters */
    enum bt type;        /* what kind of boat */
    long    sailarea;    /* sail area in square mm */
};

任何初值重要的变量都应该被显式地初始化,或者至少应该添加注释,说明依赖C的默认初始值0。空初始化"{}"应该永远不被使用。结构体初始化应 该用大括号完全括起来。用于初始化长整型(long)的常量应该使用显式长度。使用大写字母,例如2l看起来更像21,数字二十一。

复制代码 代码如下:

int   x = 1;
char  *msg = "message";
struct boat    winner[] = {
    { 40, YAWL, 6000000L },
    { 28, MOTOR, 0L },
    { 0 },
};

如果一个文件不是独立程序,而是某个工程整体的一部分,那么我们应该最大化的利用static关键字,使得函数和变量对于单个文件来说是局部范畴 的。只有在有清晰需求且无法通过其他方式实现的特殊情况时,我们才允许变量被其他文件访问。这种情况下应该使用注释明确告知使用了其他文件中的变 量;注释应该说明其他文件的名字。如果你的调试器遮蔽了你需要在调试阶段查看的静态对象,那么可以将这些变量声明为STATIC,并根据需要决定 是否#define STATIC。

最重要的类型应该被typedef,即使他们只是整型,因为独立的名字使得程序更加易读(如果只有很少的几个integer的typedef)。 结构体在声明时应该被typedef。保持结构体标志的名字与typedef后的名字相同。

复制代码 代码如下:

typedef struct splodge_t {
    int  sp_count;
    char *sp_name, *sp_alias;
} splodge_t;

总是声明函数的返回类型。如果函数原型可用,那就使用它。一个常见的错误就是忽略那些返回double的外部数学函数声明。那样的话,编译器就会 假定这些函数的返回值为一个整型数,并且将bit位逐一尽职尽责的注意转换为一个浮点数(无意义)。

"C语言的观点之一是程序员永远是对的" — Michael DeCorte

5. 函数声明

每个函数前面应该放置一段块注释,概要描述该函数做什么以及(如果不是很清晰)如何使用该函数。重要的设计决策讨论以及副作用说明也适合放在注释 中。避免提供那些代码本身可以清晰提供的信息。

函数的返回类型应该单独占据一行,(可选的)缩进一个级别。不用使用默认返回类型int;如果函数没有返回值,那么将返回类型声明为void。如 果返回值需要大段详细的说明,可以在函数之前的注释中描述;否则可以在同一行中对返回类型进行注释。函数名(以及形式参数列表)应该被单独放在一 行,从第一列开始。目的(返回值)参数一般放在第一个参数位置(从左面开始)。所有形式参数声明、局部声明以及函数体中的代码都应该缩进一级。函 数体的开始括号应该单独一行,放在开始处的第一列。

每个参数都应该被声明(不要使用默认类型int)。通常函数中每个变量的角色都应该被描述清楚,我们可以在函数注释中描述,或如果每个声明单独一 行,我们可以将注释放在同一行上。像循环计数器"i",字符串指针"s"以及用于标识字符的整数类型"c"这些简单变量都无需注释。如果一组函数 都拥有一个相似的参数或局部变量,那么在所有函数中使用同一个名字来标识这个变量是很有益处的(相反,避免在相关函数中使用一个名字标识用途不同 的变量)。不同函数中的相似参数还应该放在各个参数列表中的相同位置。

参数和局部变量的注释应该统一缩进以排成一列。局部变量声明应用一个空行与函数语句分隔开来。

当你使用或声明变长参数的函数时要小心。目前在C中尚没有真正可移植的方式处理变长参数。最好设计一个使用固定个数参数的接口。如果一定要使用变 长参数,请使用标准库中的宏来声明具有变长参数的函数。

如果函数使用了在文件中没有进行全局声明的外部变量(或函数),我们应该在函数体内部使用extern关键字单独对这些变量进行声明。

避免局部声明覆盖高级别的声明。尤其是,局部变量不应该在嵌套代码块中被重声明。虽然这在C中是合法的,但是当使用-h选项时,潜在的冲突可能性 足以让lint工具发出抱怨之声。

当前1/3页 123下一页阅读全文

时间: 2024-09-16 07:52:45

Recommended C Style and Coding Standards中文翻译版第1/3页_C 语言的相关文章

SWFUpload 2.5.0版 官方说明文档 中文翻译版

SWFUpload v2.5.0 Documentation SWFUpload 2.5.0版 官方说明文档 中文翻译版 Table of Contents 内容列表 详情请点击翻译:yukon12345 2010.6.10   SWFUpload SWFUpload 版本 2 概览 (Overview) 入门( Getting Started) js对象 (SWFUpload JavaScript Object) 构造器(Constructor) 全局变量和常量 (Globals and Co

redis2.8配置文件中文翻译版_Redis

# Redis 配置案例 #关于单位,当你需要指定内存的大小时,可以使用如下的单位来指定 #(译者注,为什么会存在1000为单位,我认为是考虑到硬盘的容量单位是以1000来进行计算而非程序中的1024) #(因此 使用 1000为单位可以进一步地精确估算出所需的实际硬盘容量) # # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 10000

jQuery Pagination Ajax分页插件(分页切换时无刷新与延迟)中文翻译版_jquery

原项目地址:http://plugins.jquery.com/project/pagination版本:v1.2源文件下载:英文原版 或中文翻译修改版 一.相关demo 基本demo页面 Ajax demo页面 参数可编辑demo页面 二.简介与说明 此jQuery插件为Ajax分页插件,一次性加载,故分页切换时无刷新与延迟,如果数据量较大不建议用此方法,因为加载会比较慢. 原插件CSS不太合理,使用浮动,故无法方便实现左右方向的定位,且未清除浮动,在中文修改版中我对其进行了优化,使其支持te

编程书籍 中文翻译版错误纠正

以前看英文原著总有点不求甚解的感觉,最近在看的<Inside the C++ Object Model>的时候,格外要求自己不光整体理解段落含义,更应该真真正正地从语法及上下文上读懂每句话.通过中英对照(一般是在重点难点看看大师的翻译),学习大师的翻译技能.顺便发现了一些自认为欠妥的翻译,记录下来并修改为我的理解,让读者可以有更多认知选择.此后凡中英对照学习过程中发现的比较重要的,晦涩的翻译问题,都将记录在此博客中,也希望以后自己水平高了也能像大师一样去翻译一本外文原著. 说明:红色字体为原文

Backbone.js 0.9.2 源码注释中文翻译版_基础知识

// Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org (function() { // 创建一个全局对象, 在浏览器中表示为window对象, 在Node.j

Cardboard Unity SDK Reference 翻译版

最近正在准备雅思,趁机翻译了Cardboard的文档,这个文档挂在谷歌官网上,不翻墙根本看不了,翻了墙这个网页也很不稳定,经常上不去,所以就搬运了过来,不过我英语水平实在有限,这次翻译也是对自己的一次锻炼,有的地方我自己明白但是也很难表述好,还请大神轻喷. 里面有很多奇怪的词汇查不到,都是我自己猜着翻译的,还有的词查到了也不知道是啥,比如"剔除掩膜"啥的-,翻译工作量也很大,都是我自己挤时间翻译的,还请大神们指出翻译的不好的地方,英文版请见我上一篇博客:http://blog.csdn

江南Style/江南风格/GANGNAM STYLE 歌曲罗马音 中文注音

很辛苦的,卤煮一个韩文不识,由于喜欢韩文歌,之前音译了一篇trouble maker的歌词学会唱了 <wbr></wbr> 地址:http://blog.sina.com.cn/s/blog_4d4490970100zb6s.html ,这回听到韩国神曲江南style甚是喜欢,用一个多小时弄了中文发音~ 原创哦不准的地方我会不断的修改~现在大家先试为快吧~~还有,,豆瓣,微博的此版本都是楼主亲自散播.,同根同源哦~~ <wbr></wbr> <wbr&

Windows下CMD中文乱码问题解决方法,设置代码页65001后仍然乱码

原文地址: http://blog.csdn.net/u011250882/article/details/48136883 在中文Windows系统中,如果一个文本文件是UTF-8编码的,那么在CMD.exe命令行窗口(所谓的DOS窗口)中不能正确显示文件中的内容.在默认情况下,命令行窗口中使用的代码页是中文或者美国的,即编码是中文字符集或者西文字符集.  如果想正确显示UTF-8字符,可以按照以下步骤操作:  1.打开CMD.exe命令行窗口  2.通过 chcp命令改变代码页,UTF-8的

VC下实现fopen支持中文的方法_C 语言

VC的fopen函数第一个参数是const char*,一旦遇到中文文件名就难以应付了,如果中文是UTF8编码的话,我们还可以用下列代码将其转换为UNICODE,然后用_wfopen函数打开文件. 代码如下: bool UTF8ToUnicode(const char* UTF8, wchar_t* strUnicode) { DWORD dwUnicodeLen; //转换后Unicode的长度 TCHAR *pwText; //保存Unicode的指针 // wchar_t* strUnic