当前位置: 首页 >> 开源文章 >> GNU编码标准
 

GNU编码标准

作者:      来源:zz     发表时间:2007-07-13     浏览次数:      字号:    

清晰地使用C语言成分

请显式地声明函数的所有参数。不要因为它们是整数就忽略它们。

对外部函数以即将随后出现在源文件中的函数的声明应该出现在靠近文件开头 (在第一个函数定义之前的某个地方)的同一个地方。或者其它的声明应该出现在头文件中。 不要在函数中放置外部声明。

在过去一种常见的做法是在同一个函数中把同一个局部变量(比如说名为tem的变量 反复地用于不同的值。但现在,更好的方式是为每个不同的目的分别定义局部变量,并且给它们以更 有意义的名字。这不仅仅是程序更容易理解,它还会被好的编译程序所优化。你还可以把对局部变量 的声明放到包含对它的使用的最小范围中。这可以把程序变得更清晰。

不要使用可以遮蔽全局标识符的局部变量和参数。

不要在跨越了行的声明中声明多个变量。在每一行中都以一个新的声明开头。例如,不应该:

 

int    foo,        bar; 

而应该:

 

int foo, bar; 

或者:

 

int foo; int bar; 

(如果它们是全局变量,在它们之中的每一个之前都应该添加一条注释。)

当你在一个if语句中嵌套了另一个if-else语句,总是用花括号把if-else括起来。因此,不要写:

 

if (foo)   if (bar)     win ();   else     lose (); 

而总是要写:

 

if (foo)   {     if (bar)       win ();     else       lose ();   } 

如果你在else语句中嵌套了一个if语句,即可以像下面那样写else if

 

if (foo)   ... else if (bar)   ... 

按照与then那部分代码相同的缩进方式缩进else if的then部分代码,也可以在 花括号中像下面那样把if嵌套起来:

 

if (foo)   ... else   {     if (bar)       ...   } 

不要在同一个声明中同时说明结构标识和变量或者结构标试和类型定义(typedef)。 单独地说明结构标试,而后用它定义变量或者定义类型。

尽力避免在if的条件中进行赋值。例如,不要写:

 

if ((foo = (char *) malloc (sizeof *foo)) == 0)   fatal ("virtual memory exhausted"); 

而要写:

 

foo = (char *) malloc (sizeof *foo); if (foo == 0)   fatal ("virtual memory exhausted"); 

不要为了通过lint的检查而把程序修改得难看。请不要加入任何关于void的强制类型转换。 没有进行类型转换的零作为空指针常量是很好的。

命名变量和函数

请在名字中使用下划线以分隔单词,以便Emacs单词命令对它们来说有用。坚持使用小写; 把大写字母留给宏和枚举常量,以及根据统一的惯例使用的前缀。

例如,你应该使用类似ignore_space_change_flag的名字;不要使用类似 iCantReadThis的名字。

用于标明一个命令行选项是否被给出的变量应该在选项含义的说明之后,而不是选项字符 之后,被命名。一条注释即应该说明选项的精确含义,还应该说明选项的字母。例如,

 

/* Ignore changes in horizontal whitespace (-b).  */ int ignore_space_change_flag; 

当你需要为常量整数值定义名字的时候,使用enum而不是`#define'。 GDB知道枚举常量。

使用14个字符或者少于14个字符的文件名,以避免无缘无故地在System V上导致问题。

使用非标准的特征

许多现有的GNU工具在兼容Unix工具的基础上提供了许多方便的扩展。在实现你的程序时是否使用 这些扩展是一个难以回答的问题。

一方面,使用扩展可以使程序变得清晰。但另一方面,除非人们可以得到其它的GNU工具,人们就 不能创建程序。这可能使得程序只能在较少类型的机器上工作。

对于某些扩展,可能可以很容易地应付上述两种选择。例如,你可以用“关键字” INLINE定义函数并且把INLINE定义成一个宏,在根据编译器确定它是被 扩展成inline或者扩展成空。

一般地,如果你能够在没有它们的情况下直截了当地完成任务,可能最好的办法是不使用扩展, 但如果扩展可以大大地改进你的工作,就使用扩展。

对这一规则的一个例外是那些运行在大量不同系统上的大规模、已经创建的程序(例如Emacs)。 使用GNU扩展将破坏这些程序。

另一个例外是那些作为编译本身的一部分的程序:这包括必须用其它编译器进行编译以构造GNU 编译工具的任何东西。如果它们需要GNU编译器,那么没有人可以在没有安装它们的情况下编译它们。 这将是不好的。

由于大部分计算机系统还没有实现标准C,使用标准C的特征,所以使用标准C就相当于使用 GNU扩展,所以前面的考虑也适用于它。 (除了那些令我们失望的标准特征,例如三元组序列(trigraphs)--永远不要使用它们。)

三元组序列是标准C中为了弥补某些终端上可用字符的不足而提供的、用三个字符组合代替 一个特殊字符的方法。所有可用的三元组为:“??=”转换成“#”、“??/”转换成“\”、 “??'”转换成“^”、“??(”转换成“[”、“??)”转换成“]”、“??!”转换成“|”、“??<”转换成“{”、 “??>”转换成“}”、“??-”转换成“~”。

---- 译者注

适用于所有程序的程序行为

通过动态地分配所有的数据结构来避免对任何数据结构,包括变量名、行、文件和符号,的 长度和数量施加任何限制。在大多数Unix工具中,“长行被没有提示地截断”了。对于GNU工具 来说,这是不可接受的。

读入文件的工具不应该放弃NUL字符、或者任何不可打印的字符,包括那些大于0177的字符。 唯一明智的例外是那些为访问与不能处理这些字符的特定类型的打印机的界面而设计的工具。

为每个系统调用的返回值进行错误检查,除非你知道你希望忽略错误。把那些系统错误文字 (来自于perror或者它的等价物)包括在每个有失败的系统调用导致的 错误消息中,如果有的话还要包括文件名和工具名。仅仅给出“cannot open foo.c”或者“stat failed” 是不够的。

检查每个对malloc或者realloc的调用以察看它是否返回0。即使在 realloc使块变小的时候,也要检查它的返回值;在有些系统中总是把块的大小扩大到2的幂次。 如果你申请更少的空间,realloc可能得到一个不同的块。

在Unix中,如果realloc返回0,那么它就可以破坏存储块。GNU realloc 没有这个错误:如果它失败了,原来的块不会被改变。放心地假定这个错误已经被修正了。如果你希望在 Unix上运行你的程序,并且在这种情况下不希望失去内存块,你可以使用GNU malloc

你必须假定free将改变被释放的块的内容。任何你希望从块中获得的东西,你必须在 调用free之前拿到它。

使用getopt_long对参数进行解码,除非参数的语法使得这样做变得不合情理。

当静态内存在程序执行的时候被写入的情况下,显式地使用C代码来初始化它。保留对那些不会被改变 的数据的C初始化声明。

尽力避免访问晦涩的Unix数据结构的低级界面(例如文件目录、utmp或者内核内存的分布), 因为它们通常会降低兼容性。如果你希望找到目录中的所有文件,使用readdir或者其它 高级的界面。GNU兼容将地支持它们。

在缺省状态下,GNU系统将提供BSD的信号处理函数和POSIX的信号处理函数。因此GNU软件应该使用它们。

在错误中检测到“不可能”的条件是,只要退出就行。没有理由打印任何消息。这些检查表明有bug存在。 任何希望修正错误的人都必须阅读源代码并且运行调试器。所以在源代码中通过注释给出问题的解释。相关的 数据将储存在变量中,这些变量很容易被调试器检测到,所以没有理由把它们转移的其它任何地方。

格式化错误信息

来自于编译器的错误信息应该使用格式:

 

source-file-name:lineno: message 

如果有适当的源文件存在,则来自于非交互式程序的错误信息应该使用格式:

 

program:source-file-name:lineno: message 

或者,如果没有相关的源文件,则应该使用格式:

 

program: message 

在一个交互式程序(从终端读入命令的程序)中,不把程序名包括在错误信息中更好一些。指明程序正在 运行的地方应该是提示符或者屏幕的布局。(当相同的程序在运行时从源文件中,而不是从终端中读取输入, 它就不是交互式的了,并且最好按照非交互方式风格打印错误信息。)

在字符串message被放置在程序名和/或文件名之后时,它不应该以大写字母开头。此外,它 也不应该以句点结尾。

来自于交互式程序的错误信息,以及其它的诸如使用信息的信息,应该以大写字母开头。但它们不应该 以句点结尾。

库的行为

试着使库函数成为可再入的。如果它们需要进行动态内存分配,至少要试图避免任何来自 malloc本身的非可再入的方面。

这里给出了一些库的命名惯例,以避免名字的冲突。

为库选择一个多于两个字符的命名前缀。所有的外部函数和变量名都应该以这个前缀开头。 还有,在任何给定的库成员中,仅仅应该含有一个函数或者变量。这通常意味着要把每个函数 和变量都放在单独的源文件中。

一个例外是如果两个符号总是在一起使用,而使得没有任何合理的程序只需要使用其中的 一个而不使用另外一个;那么它们可以被放在同一个文件中。

标示没有在文档中给予说明的调用点的外部符号的名字应该以`_'开头。它们 还要包括为库选择的名字前缀,以防止与其它库可能产生的冲突。如果你愿意,它们可以和 用户调用点放在同一个文件中。

你可以按照你的意愿使用静态函数和静态变量并且它们不需要服从任何命名惯例。

适用于GNU的移植性

在Unix世界中,“移植性”往往指的是移植到不同的Unix版本中。对于GNU软件来说是次要的, 这是因为它们的主要目的是运行在GNU内核上而且仅仅运行与其上,使用GNU C编译器编译并且仅仅 由它进行编译。不同cpu上的各种GNU系统的变种数量将象不同cpu上的Berkeley 4.3系统的变种一样多。

今天所有的用户都在非GNU系统上运行GNU软件。所以有必要支持各种非GNU系统;但不是特别重要。 在合理范围的系统上获得可移植性的最简单方式是使用Autoconf。由于大部分程序需要知道的关于 主机的知识已经由Autoconf写下来了,所以你的程序不太可能需要知道比Autoconf所能够提供的知识 的更多知识。

因为目前GNU内核还没有被完成,所以还难以确定GNU内核将提供那些工具。因此,就假定你可以使用 你在4.3中可以使用的任何东西;只要避免使用有更高级的替代结构(readdir)存在的半内部的数据结构 (例如,directories)就可以了。

你可以自由地假设任何合理C语言标准工具、库或者内核,因为我们将发现有必要在完整的GNU系统 中支持它们,而不论我们是否已经这样做了。一些现有的内核或者C编译器所缺少的功能与GNU内核和 C编译器对它们的支持没有关系。

另外一个需要担心的是cpy类型间的不同,例如字节序(byte order)的不同和对齐限制的不同。 16位的机器恐怕不会被GNU所支持,所以没有必要花费任何时间去考虑整数少于32位的可能性。

你可以假定所有的指针都具有相同的格式,而不论它们指向什么,并且它们实际上都是一个整数。 在一些怪异的机器上不是这样,但它们并不重要;不要为迎合它们而浪费时间。 此外,我们最终将把函数原型放到所有的GNU程序中,而那将可能使你的程序即使在怪异的机器上也能够工作。

因为一些重要的机器(包括68000)是高位开头(big-endian),不能假定整数对象的地址就是最低位 字节(least-significant)的地址是十分重要的。因此,不要犯如下的错误:

 

int c; ... while ((c = getchar()) != EOF)         write(file_descriptor, &c, 1); 

你可以假定使用一兆内存是合理的。除非可以在那个层次可以做到,不要为减少内存的使用而费力。 如果你的程序创建了复杂的数据结构,就把它们存放在内核中,并且在malloc返回0的时候给出一个致命错误 就行了。

如果一个程序按照行工作并且可以被用于任何用户提供的输入文件,它就应该在内存中 仅仅保存一行,因为这并不十分困难并且用户将需要能够操作比内核一次能够处理的文件更大的输入文件。

命令行界面标准

请不要让工具的行为依赖于调用它的名字。有时需要把一个工具连接到不同的名字, 并且这将不会改变它的功能。

最为替代,可以在运行时使用选项或者编译器选项,或者同时使用两者来选择不同的程序行为。

服从POSIX关于程序的命令行选项的指导是一个好主意。这样做的最简单方式是使用getopt 来分析选项。需要指出的是,除非使用了特殊参数`--',GNU版本的getopt 通常允许选项出现在参数中的任何位置。这不是POSIX的规定;它是GNU的扩展。

请为Unix风格的单字符选项定义等价的长名字选项。我们希望以这种方式使GNU对用户更加友好。 通过使用GNU函数getopt_long很容易做到这一点。

通常仅仅把最为普通参数给出的文件名当作输入文件是一个好主意;所有输出文件都应该通过选项给出 (使用`-o'更好)。即使你为了保持兼容性而允许把普通参数当作输出文件名,你仍然可以 试图为输出文件名提供一个合适的选项。这将为GNU工具带来更多的一致性,以减少用户需要记忆的特征。

程序还应该支持一个用于输出程序的版本号的选项`--version',以及支持一个用于输出 选项用法信息的选项`--help'

为程序制作文档

为GNU程序制作文档请使用Texinfo。参见Texinfo手册,硬拷贝或者GNU Emacs Info子系统 中的版本都行。(C-h i)。作为例子,可以看看现有的GNU Texinfo文件 (例如,在GNU Emacs发布版本中`man/'目录下的Texinfo文件)。

手册的标题页应该说明本手册适用于程序的那个版本。手册的顶节点也应该包含这个信息。 如果手册比程序改变得还要快,后者与程序是无关的,请在上述两个地方说明手册的版本号。

手册应该说明所有的命令行参数和所有的命令。手册应该给出使用它们的例子。但不要把 手册组织成一个特征的列表。相反,在手册中按照把用户需要理解的概念放在特征之前的方式组织文档。 说明用户可能需要达到的目的,并且解释如何完成它们。不要把Unix man手册作为书写GNU文档 的模式;它们不是值得模仿的好例子。

手册应该有一个名为`program Invocation'或者 `program Invoke'或者`Invoking program' 的节点,其中program是该程序的程序名,也就是在shell中输入以运行程序的那个名字。 该节点(如果它有子节点,也包括子节点)应该说明程序的命令行参数以及如何运行它(也就是 人们将在man手册中看到的那一类信息)。以`@example'开头包含一个程序可以使用的 所有选项和参数的模板。

另一种方式是,在某些菜单中添加一个名字符合上述模式之一的菜单项。它表明由菜单项指出 的节点就是为此而创建的,而不管这个节点的实际名字是什么。

将会有一个自动的功能使得用户可以给出程序名并且只需要快速地阅读手册的这个部分。

如果一个手册说明了多个程序,那么手册应该为每个它所说明的程序定义一个这样的节点。

除了程序包的手册之外,包还应该包含一个名为`NEWS'的文件,它包含了一个对用户来说 是可见的、并且值得一提的修改。在每个新的发行版本中,在文件的前面添加新的条目并且指出适用于 它们的版本。不要删除原来的条目;把它们保留在新条目的后面。按照这种方式,从以前的版本升级的 用户就可以看到有那些新的功能。

如果`NEWS'文件变得太长了,可以把一些陈旧的条目放到一个名为`ONEWS' 的文件中,并且在`NEWS'文件的结尾加一个说明以告知用户参考`ONEWS'

如果你愿意,你可以在提供Texinfo手册的同时提供man手册。但请记住维护man手册需要在每次程序 改变的时候都付出努力。你花费在man手册上的任何时间都消耗了你本来能够用在贡献更有价值的东西 上的时间。

因此,即使用户自愿提供man手册,你可能会发现这个礼物太麻烦而不值得接受。除非你手头有时间, 并且除非有志愿者愿意承担维护它的全部责任--以至于你可以把它完整地交给他,拒绝提供man手册可能 会更好一些。如果志愿者停止维护man手册,那么也不必感到有责任让你自己承担它;在其它的出现志愿者 维护它之前撤销man手册也许更好一些。

另一种方式是,如果你希望man手册的内容与实际情况区别很小而使得man手册仍然有用,可以在man 手册的开头给出显著的声明以说明你没有维护它并且Texinfo手册是更加权威的,同时指出如何访问 Texinfo文档。

制作发行包

发行Foo的版本69.96的tar文件包的名字是`foo-69.96.tar'。它应该被解包到名为 `foo-69.96'的子目录中。

对程序的创建和安装不应该修改发布版本中的任何文件。这意味着以任何方式作为程序的正式部分的 所有文件都必须被分成源文件非源文件两类。源文件由人手工 编写并且不会被自动改变;非源文件则在Makefile的控制下由程序从源文件生成。

自然地,所有源文件必须出现在发布版本中。只有在非源文件不是过时的并且与机器是无关的情况下, 从而在创建发布版本时将不会需要修改它们,才能把非源文件包含在发布版本中。我们一般把由Bison、 Lex、TeX和Makeinfo生成的非源文件包括进去;这有助于避免在我们的发布版本中引入不必要的依赖性, 以使得用户可以安装他们需要安装的包。

永远不要把实际上可能在程序的创建和安装中被修改的非源文件包含在发布版本中。 所以如果你发布非源文件,在你作新的发布时,总是要确认它们没有过时。

确保从发布包中解开的目录(以及所有的子目录)对于所有人来说都是可写的(八进制模式777)。 这样做是为了使那些保留从tar包中取出的文件的所有权(ownership)和许可权(permissions)的老版本的 tar,即使在用户没有授权的情况下也能够提取出所有的文件。

确保在发布版本中的没有多于14个字符的文件名。同样地,由程序创建的文件都不含有长于14个字符的 文件名。这样做的原因是有些系统坚持POSIX标准的愚蠢解释,并且拒绝打开长文件名,而不是象在过去那样 把文件名截短。

不要在发布版本本身包含任何符号连接。如果tar文件包括符号连接,那么人们甚至不能在那些不能支持 符号连接的系统上打开包。还有,不要在不同的目录中为一个文件使用多个名字,因为某些文件系统不能 处理它并且这使得包不能在这类文件系统上被打开。

试着确保所有的文件名在MS-DOG下都是唯一的。在MS-DOG下文件名由8个字符组成,后面还可以附加 一个点和至多三个字符。MS-DOG将截断点之前和之后的多余字符。因此,`foobarhacker.c'`foobarhacker.o'不会被混淆;它们被截断成`foobarha.c'`foobarha.o' ,它们是截然不同的。

在你的发布版本中包含一个你用来测试打印所有`*.texinfo'文件的`texinfo.tex'的副本。

同样地,如果你的程序使用了诸如regex、getopt、obstack或者termcap之类的小GNU软件包,把它们包括在 发布文件中。把它们排除在外将使发布文件小一些,其代价是给那些不知道需要哪些额外文件的用户带来不便。

[1] [2] [3]

责任编辑 webmaster

 
 
 
 
 
评论更多>>
 
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •