现在的位置: 首页 > 综合 > 正文

GNU编码标准

2014年02月24日 ⁄ 综合 ⁄ 共 19994字 ⁄ 字号 评论关闭

//来源不详

GNU编码标准

引用私有程序
接受他人的奉献
修改日志(Change Logs)
与其它实现的兼容性
Makefile惯例
Makefile的通用惯例
Makefile中的工具
为用户提供的标准目标
为指明命令而提供的变量
为安装目录而提供的变量
配置是如何进行的
使用C以外的语言
格式化你的源代码
为你的工作写注释
清晰地使用C语言成分
命名变量和函数
使用非标准的特征
适用于所有程序的程序行为
格式化错误信息
库的行为
适用于GNU的移植性
命令行界面标准
为程序制作文档
制作发行包

--------------------------------------------------------------------------------

GNU编码标准
GNU编码标准@author{Richard Stallman} @author{last updated 03 Feb 1993}

Copyright (C) 1992, 1993 Free Software Foundation

Permission is granted to make and distribute verbatim copies of this manual provided the

copyright notice and this permission notice are preserved on all copies.

Permission is granted to copy and distribute modified versions of this manual under the

conditions for verbatim copying, provided that the entire resulting derived work is

distributed under the terms of a permission notice identical to this one.

Permission is granted to copy and distribute translations of this manual into another

language, under the above conditions for modified versions, except that this permission

notice may be stated in a translation approved by Free Software Foundation.

--------------------------------------------------------------------------------

本文由王立翻译. 1999.11.9

--------------------------------------------------------------------------------

引用私有程序
不要在任何情况下,为你在的GNU中的工作或者在工作中引用Unix的源代码(或者任何其它私有程序)。

如果你对一个Unix程序内容有一些模糊的记忆,这并不因为着你绝对写程序来模仿它, 但请试图在内

部使用不同的代码行来组织它,因为这将使你工作的结果在细节上与Unix版本有所不同。

例如,Unix工具通常进行了优化以使用最少的内存;如果你更希望提高速度,你的程序将会有很大的不

同。 你可以在内核中保存整个输入文件并且在内存中扫描而不是使用stdio。使用比Unix程序更新的、

更明智的算法。不使用暂时文件。在一遍扫描而不是两遍扫描中完成任务(我在assembler(汇编器)

中这样做了)。

或者相反,强调简单性而不是速度。对于一些应用程序来说,今天的计算机只要使用简单的算法就够了

或者注重一般性。例如,Unix程序通常使用静态的表格和固定大小的字符串,这导致了不可改变的限制

; 用动态分配来代替。确认你的程序处理了输入文件为空和其它滑稽的情况。为增加扩展性而增加一

种 程序语言并且用那种语言完成程序的一个部分。

或者把程序的一部分修改成独立的库。或者用一个简单的废物收集器而不是在释放内存的时候精确地进

行跟踪, 或者使用诸如obstacks这样的新的GNU工具。

接受他人的奉献
如果其他人发给你一段添加到你正在编写程序中的代码,我们需要准许使用它的法律文书 --我们将需

要从你那里取得同样的法律文书。程序的每个重要的贡献者都必须签署 某种法律文书以使得我们可以

给程序一个清晰的标题。仅有主要作者是不够的。

所以,在把来自于他人的任何共享添加到程序中之前,告诉我们以便我们可以做出安排 以获取文书。

在你实际地使用贡献之前,请等待直到我们告诉你我们已经收到了签署的文书。

这即适用于你发行程序之前也适用于发行之后。如果你收到了一个修正bug的补丁,并且 它们做了主要

的修改,我们就需要为他提供法律文书。

你不需要为这里或者那里的少数几行修改提供文书,因为对于达到版权目的没有意义。 还有,如果你

从建议中获得的仅仅是一些想法,而不是你实际上使用的代码,你也不需要文书。 例如,如果你写了

一个程序的不同解决方案,你并不需要获得许可文书。

我知道这是十分麻烦的;它对我们来说也十分麻烦。但如果你不等待,你就可能误入歧途, 如果这个

贡献者的雇主不肯签署弃权声明怎么办?你可能不得不再次把代码剔除出来!

最糟糕的情况是如果你忘记告诉我们其它的贡献者,我们可能会因此而窘迫地出现在法庭上。

修改日志(Change Logs)
为每个目录维护一个修改日志,以记述对这个目录下源文件的修改。这样做的目的是使得 在将来寻找

bug的人可以指导大致是那些修改导致了错误。通常,一个新的bug可以在最近进行 的修改中被找到。

更重要的事,修改日志有助于消除程序的不同部分之间在概念上的不一致性; 它们可以告诉我们概念

冲突产生的历史。

使用Emacs命令M-x add-change在修改日之中创建一个新的条目。一个条目应该 包含一个星号、被修改

的文件的名称以及被扩在括号内的、被修改了的函数、变量或者任何东西。 括号之后是冒号和对你对

函数或变量的修改的说明。

用空行把无关的条目分隔开。如果两个条目反映了同一个修改,因而它们一同工作,那就不要 在它们

之间使用空行。如果后续的条目针对的是相同的文件,那么你可以忽略文件名的星号。

下面是一些例子:

* register.el (insert-register): Return nil. (jump-to-register): Likewise.  * sort.el

(sort-subr): Return nil.  * tex-mode.el (tex-bibtex-file, tex-file, tex-region): Restart

the tex shell if process is gone or stopped. (tex-shell-running): New function.  * expr.c

(store_one_arg): Round size up for move_block_to_reg. (expand_call): Round up when

emitting USE insns. * stmt.c (assign_parms): Round size up for move_block_from_reg.
在这里没有必要叙述修改的完整目录和它们是如何协同工作的。把这些说明作为注释放到 代码中更好

一些。这就是说为什么只要给出“New function”就够了;在源代码中,与函数 放在一起的注释说明

了它是做什么的。

然而,有时为一大堆修改写上一行文字以描述它们的整体目的是有用的。

在概念上,你可以把修改日志看作解释原始版本与当前版本的不同的“undo列表”。 人们可以阅读当

前的版本;他们不需要修改日志告诉他们其中有什么。他们从修改日之中 得到的是关于早期版本的不

同的清晰解释。

在你以简单的方式修改函数的调用顺序,并且你修改了所有对函数的调用时,不必为所有 的调用创建

单独的条目。只要在被调用的函数的条目中写“All callers changed.”即可。

在你仅仅修改了注释或者文档字符串的时候,为该文件写一个条目,而不必提到函数, 就足够了。只

要写"Doc fix."。不必为文档文件维护修改日志。这是因为文档不那么容易 受到难以修正的错误的影

响。文档不是由那些必须以精确地工程方式相互作用的部分组成的; 要修改一个错误,你不需要知道

这个错误传播的历史。

与其它实现的兼容性
作为一个特例,对于GNU中的工具程序和库,它们应该和Berkeley Unix相应的部分向上兼容, 如果标

准C定义了它们的行为,那它们应该和标准C向上兼容,如果POSIX规范定义了它们的行为, 那它们也应

该与POSIX规范向上兼容。

当这些标准发生冲突的时候,为每个标准提供兼容模式是有用的。

标准C和POSIX禁止进行任何形式的扩展。自由地进行你的扩展,并且把选项 `--ansi'或`--

compatible'包括进来以关闭你的扩展。 但是如果扩展很可能导致任何实际程序或者脚本的崩溃,那么

它可能实际上不是向上兼容的。 尝试一下重新定义它的界面。

当一个特征仅仅被用户(而不会被程序或者命令文件)所使用的时候,并且在Unix中它完成得 并不好

,请自由地用完全不同并且更好的方式代替它。(例如,用Emacs代替vi。)但同时提供兼容 模式仍然

是很好的。(现在有自由的vi实现,所以我们提供了它。)

欢迎提供Berkeley Unix没有提供的有用功能。Unix中没有的附加功能可能是有用的,但我们 优先复制

那些Unix已经有的功能。

Makefile惯例
本章叙述为GNU程序书写Makefile的惯例。

Makefile的通用惯例
每个Makefile都应该包含这一行:

SHELL = /bin/sh
以避免那些由从环境中继承SHELL变量的系统带来的麻烦。 (GNU make永远不会出现这个问题。)

不要假定`.'出现在用于寻找可执行的命令的路径中。当你需要在make期间运行作为你的 包的一部分的

程序时,如果程序是作为make的一部分而创建的,请确保它使用了`./',或者 如果文件是不会被改变

的源代码的一部分,请确保它使用了`$(srcdir)/'。

如果运行`configure'时使用了选项`--srcdir',那么`./'与 `$(srcdir)/'之间的区别就十分重要。一

下形式的规则:

foo.1 : foo.man sedscript         sed -e sedscript foo.man > foo.1
将在当前目录不是源代码目录的情况下导致错误,这是因为`foo.man'和`sedscript' 不在当前目录中

在使用GNU make的时候,由于不论源文件在那里,`make'的自动变量`$<' 都将表示它,所以在只存在

一个依赖文件的情况下,依靠`VPATH'来寻找源文件仍然是可行的。 (许多版本的make只在隐含规则中

设置`$<'。)如下的makefile目标:

foo.o : bar.c         $(CC) -I. -I$(srcdir) $(CFLAGS) -c bar.c -o foo.o
将被如下目标所替代:

foo.o : bar.c         $(CC) $(CFLAGS) ___FCKpd___4lt; -o $@
以便使`VPATH'能够正确地工作。当目标含有多的依赖文件时,显式地使用`$(srcdir)' 让规则正常工

作的最简单办法。例如,上述为`foo.1'而提供的目标最好被写作:

foo.1 : foo.man sedscript         sed -s $(srcdir)/sedscript $(srcdir)/foo.man > foo.1

Makefile中的工具
书写能够在sh,而不是在csh,中运行的Makefile命令(以及任何shell脚本,例如 configure)。不要

使用任何ksh或者bash特殊的功能。

为创建和安装而提供的configure脚本和Makefile规则不要直接使用任何工具,除了以下的几个之外:

cat cmp cp echo egrep expr grep ln mkdir mv pwd rm rmdir sed test touch
坚持使用这些程序通常支持的选项。例如,因为许多系统不支持`mkdir -p',尽管它可能有些方便,

但不要使用它。

为创建和安装而提供的Makefile规则还可以使用编译器和相关的程序,但应该通过make变量以便 用户

对它们进行替换。下面是一些我们所说的相关的程序:

ar bison cc flex install ld lex make makeinfo ranlib texi2dvi yacc
在你使用ranlib的时候,你应该测试它是否存在,并且仅仅在它存在的情况下运行它,以使得发布 版

本在那些没有ranlib的系统中也能够工作。

如果你使用了符号连接,你应该为没有符号连接的系统实现一个替代手段。

你可以在只打算用于特定系统的Makefile的部分(或者脚本)中使用你能够确认在那些系统上存在的工

具。

为用户提供的标准目标
所有的GNU程序应该在它们的Makefile中含有下列目标:

`all'
编译整个程序。它应该是缺省目标。这个目标不需要重新创建任何文档文件;Info文件被包含在发布版

本中, 同时,只有在用户明确地要求创建DVI文件的时候才创建DVI文件。
 

`install'
编译程序并且把可执行文件、库文件等文件复制到它们在实际应用中应该存在的位置。如果存在一个可

以 检测程序是否被正确地安装了的简单测试,本目标将首先运行这个测试。
如果文件的安装目录不存在,该命令将创建这样的目录。它们包括由变量prefix和 exec_prefix的值指

明的目录,以及需要的所有目录。完成该任务的一种方式是按照后面所说明的 方式通过目标

installdirs来完成。

在任何用户安装man手册的命令之前使用`-',以使得make忽略所有的错误。 错误将在那些没有安装

Unix man手册文档系统的系统中出现。

安装Info文件的方式是用$(INSTALL_DATA)把它们复制到`$(infodir)'中。 (参见为指明命令而提供的

变量),并且如果有程序install-info存在, 那么就运行它。install-info是一个脚本,它编辑Info

`dir'文件以把给定的Info文件添加 或者更新目录项的脚本;它将是Texinfo包的一个部分。下面是用

于安装一个Info文件一个简单规则:

$(infodir)/foo.info: foo.info # There may be a newer info file in . than in srcdir.       

 -if test -f foo.info; then d=.; /          else d=$(srcdir); fi; /        

$(INSTALL_DATA) $d/foo.info $@; / # Run install-info only if it exists. # Use `if' instead

of just prepending `-' to the # line so we notice real errors from install-info. # We use

`$(SHELL) -c' because some shells do not # fail gracefully when there is an unknown

command.         if $(SHELL) -c 'install-info --version' /            >/dev/null 2>&1;

then /           install-info --infodir=$(infodir) $d/foo.info; /         else true; fi
 

`uninstall'
删除所有由`install'目标创建的所有安装的文件(但不包括那些由诸如`make all' 之类的目标创建的

,没有被安装的文件)。
 

`clean'
从当前目录中删除所有在创建程序过程中创建的文件。不要删除那些纪录配置情况的文件。有些文件可

能是在创建 过程中创建的,但因为它们是和发布版本一起发布的,通常不是在创建过程中创建的,这

样的文件也需要保留下来。

如果`.dvi'文件不是发布版本的一部分,就删除它们。

`distclean'
从当前目录中删除所有在程序的配置和创建过程中创建的文件。如果你解包源代码并且在没有添加任何

其它文件的 情况下创建程序,`make distclean'将仅仅保留那些出现在发布版本中的文件。
 

`mostlyclean'
类似于`clean',可能不会删除少数人们通常不希望重新编译的文件。例如,GCC的 `mostlyclean'目标

不会删除`libgcc.a',这是因为很少需要重新编译并且重新编译将花费大量的时间。
 

`realclean'
从当前目录中删除所有可以由Makefile重新创建的文件。这通常包括所有由distclean删除的文件, 以

及:由Bison生成的C源文件、标记表(tags tables)、Info文件等等。
然而有一个例外:即使`configure'可以通过使用Makefile中的规则重新创建,`make realclean' 也不

会删除`configure'。更一般地说,`make realclean'将不会删除为了运行`configure' 而存在的任何

东西,并且随后开始创建程序。

`TAGS'
为本程序更新标记表(tags table)。
 

`info'
生成所有需要的Info文件。书写该规则的最佳方式是:
 

info: foo.info  foo.info: foo.texi chap1.texi chap2.texi         $(MAKEINFO)

$(srcdir)/foo.texi
你必须在Makefile中定义变量MAKEINFO。它应该运行程序makeinfo,该程序是Texinfo发布 版本的一部

分。

`dvi'
Generate DVI files for all TeXinfo documentation. For example:
 

dvi: foo.dvi  foo.dvi: foo.texi chap1.texi chap2.texi         $(TEXI2DVI)

$(srcdir)/foo.texi
你必须在Makefile中定义变量TEXI2DVI。它应该运行程序texi2dvi,该程序也是Texinfo 发布版本的一

部分。作为另一个选择,只要写依赖文件并且允许GNU Make提供这个命令就行了。

`dist'
为本程序创建一个发布版本tar文件。该tar文件将被设置以使得在tar文件中的文件名以子目录名开头

,这个子目录名 是包用于发布的名字。这个名字可以包含版本号。
例如,GCC版本1.40的发布tar文件将被解包到名为`gcc-1.40'的子目录中。

完成该任务的最简单方式是以适当的名称创建一个子目录,使用ln或者cp把正确的文件 安装到该目录

中,而后tar这个子目录。

目标dist应该显式地依赖于发布版本中所有的非源文件,以确保它们在发布版本中都不是过时的。参见

制作发布包

`check'
(如果有的话)执行自检测。用户必须在运行测试之前,但不必在安装程序之前创建程序;你应该写下

自检测以便 它们在程序创建之后而没有被安装之前进行工作。
对于那些适用于以下的目标的程序,建议你按照常用的名字提供它们。

installcheck
(如果有的话)执行安装监测。用户必须在运行该检测之前创建并且安装程序。你不应该假定

`$(bindir)' 出现在搜索路径中。
 

installdirs
添加一个名为`installdirs'的目标,以便创建安装文件的目录和它们的父目录。 有一个称为

`mkinstalldirs'的脚本可以为此提供便利;在Texinfo包中可以找到它。你可以使用象下面那样的规则


 

# Make sure all installation directories (e.g. $(bindir)) # actually exist by making them

if necessary. installdirs: mkinstalldirs         $(srcdir)/mkinstalldirs $(bindir)

$(datadir) /                                 $(libdir) $(infodir) /                       

         $(mandir)

为指明命令而提供的变量
Makefile应该提供变量以覆盖某些命令、选项等等。

特别地,你应该通过变量来运行大部分工具程序。因此,如果你使用了Bison,就定义一个缺省值是通

过`BISON = bison' 来设定的变量BISON,并且在你需要使用Bison的所有地方通过$(BISON)引用它。

在这种方式下,文件管理工具:ln、rm、mv等等并不需要通过变量引用,这是因为 用户不需要用其它

程序来替代它们。

每个程序名变量都应该有一个对应的变量以便为程序提供选项。把`FLAGS'附加到程序名变量名的后面

就是 选项变量名--例如,BISONFLAGS。(名字CFLAGS是这项规则的一个例外,但因为它是标准的而保

留了它。) 在任何运行预处理器的编译命令中使用CPPFLAGS,在任何进行连接的编译命令和任何对ld

的直接使用中 使用LDFLAGS。

如果存在一些为了正确地编译某些文件而必须使用的C编译器选项,不要把它们包括在CFLAGS中。用户

希望 能够自由地指明CFLAGS的值。替代的方式是:通过在编译命令行中显式地给出这些必要的选项或

者通过定义一条隐含 规则,从而以独立于CFLAGS的方式把选项传递给C编译器。

CFLAGS = -g ALL_CFLAGS = -I. $(CFLAGS) .c.o:         $(CC) -c $(CPPFLAGS) $(ALL_CFLAGS)

___FCKpd___12lt;
把选项`-g'包括在CFLAGS中,因为它对于正确的编译来说并不是必要的。 你可以认为它仅仅是关于缺

省值的一个建议。如果包被设置成在缺省的状态下由GCC编译,那么你可能还需要把`-O' 包括在CFLAGS

的缺省值之中。

把CFLAGS放在编译命令行的最后,就是在其它包含了编译选项的变量之后,以便于用户使用CFLAGS 来

覆盖其它的选项。

每个Makefile都应该定义变量INSTALL,它是把一个文件安装到系统中的基本命令。

每个Makefile还应该定义变量INSTALL_PROGRAM和INSTALL_DATA。(两者的缺省值都应该是 $(INSTALL)

。)而后,Makefile应该使用这些变量作为实际安装的命令,分别用于安装可执行文件和不可执行的文

件。 按照下面的方式使用这些变量:

$(INSTALL_PROGRAM) foo $(bindir)/foo $(INSTALL_DATA) libfoo.a $(libdir)/libfoo.a
总是把文件名,而不是目录名,作为安装命令的第二个参数。为每个需要安装的文件使用独立的命令。

为安装目录而提供的变量
安装目录总是应该通过变量来命名,以易于把包安装在其它非标准的位置。这些变量的标准名字是:

`prefix'
用于构造下列变量的缺省值的前缀。prefix的缺省值应该是`/usr/local'(至少现在是它)。
 

`exec_prefix'
用于构造下列某些变量的缺省值的前缀。exec_prefix的缺省值应该是$(prefix)。
一般来说,$(exec_prefix)指的是用于储存与机器有关的文件(比如说可执行文件和子程序库)的目录

, 而$(prefix)则被直接用于其它目录。

`bindir'
用于储存用户可以运行的可执行程序的目录。一般来说应该是`/usr/local/bin',但应该被写作

`$(exec_prefix)/bin'。
 

`libdir'
用于安装由程序运行,而不是由用户运行的可执行文件的目录。Object文件和object代码库也应该被储

存在这个目录。 提供该目录的意图是为了储存适用于特殊机器结构,但又不必出现在命令路径中的文

件。libdir的值通常是 `/usr/local/lib',但应该被写作`$(exec_prefix)/lib'。
 

`datadir'
用于安装程序在运行时需要访问的只读数据文件的目录。该目录用于储存与使用的机器独立的文件。它

通常是 `/usr/local/lib',但应该被写作`$(prefix)/lib'。
 

`statedir'
用于安装程序在运行时需要修改的数据文件的目录。这些文件应该与使用的机器类型独立,并且应该可

以在网络 安装的情况下载不同的机器之间共享。它通常应该是`/usr/local/lib',但应该被写作

`$(prefix)/lib'。
 

`includedir'
用于储存将被用户程序以C预处理指令`#include'引入的头文件的目录。它通常应该是

`/usr/local/include',但应该被写作`$(prefix)/include'。
除了GCC以外,大部分编译器并不在`/usr/local/include'中寻找头文件。所以以这种方式安装头文件

仅仅 适用于GCC。但有些库被设计成与其它编译器共同工作。它们应该在两个地方安装它们的头文件,

一个由includedir 给出,另一个由oldincludedir给出。

`oldincludedir'
为除了GCC之外的其它编译器安装头文件的目录。这通常应该是`/usr/include'。
Makefile命令应该检测oldincludedir的值是否为空。如果为空,Makefile命令就不应该试图使用

oldincludedir;Makefile命令应该放弃对头文件的第二个安装。

除非头文件来自于同一个包,包不应该替换已经存在的头文件。因此,如果你的Foo包提供了一个头文

件`foo.h', 并且如果它没有出现在oldincludedir目录中或者oldincludedir目录中的`foo.h'也是来

自 与Foo包,那么Foo包就应该把头文件安装到oldincludedir中。

为了判定`foo.h'是否来自于Foo包,可把一个特殊的字符串作为注释的一部分放在文件中,而后用grep

搜索这个 字符串。

`mandir'
(如果存在)本包安装man手册的目录。它应该包含对应于正确的手册部分的后缀-- 对于一个工具来说

通常是`1'。它一般是`/usr/local/man/man1',但你 应该把它写成:`$(prefix)/man/man1'。
 

`man1dir'
安装man手册第一部分的目录。
`man2dir'
安装man手册第二部分的目录。
`...'
如果包需要把man手册安装到手册系统的多个部分,就用这些名字来代替`mandir'。
不要把man手册作为GNU软件的主要文档。用Texinfo书写文档来代替它。Man手册只是因为 人们在Unix

,它只是一个次要的应用程序,上运行GNU软件才存在的。

`manext'
作为需要安装的man手册的文件的扩展名。它应该是一个点加上一个适当的数字;通常它应该是:`.1'


 

`man1ext'
将被安装到man手册第一部分的文件的扩展名。
`man2ext'
将被安装到man手册第二部分的文件的扩展名。
`...'
如果包需要把man手册安装到手册系统的多个部分,就用这些名字代替`manext'。
 

`infodir'
为本包安装Info文件的目录。在缺省状态下,它应该是`/usr/local/info',当它应该被写成

`$(prefix)/info'。
 

`srcdir'
用于编译源代码的目录。该变量的值通常是由configureshell脚本插入的。
例如:

# Common prefix for installation directories. # NOTE: This directory must exist when you

start the install. prefix = /usr/local exec_prefix = $(prefix) # Where to put the

executable for the command `gcc'. bindir = $(exec_prefix)/bin # Where to put the

directories used by the compiler. libdir = $(exec_prefix)/lib # Where to put the Info

files. infodir = $(prefix)/info
如果你的程序在一个标准的用户给定的目录中安装了大量的文件,可能把为程序特别提供的文件存放到

子目录中会有用一些。如果你这样做了,你应该改写install规则以创建这些子目录。

不要指望用户会把子目录名包括在上面列出的变量的值中。为安装目录提供统一的变量名集合的意图是

使得用户可以为一些不同的GNU包指明完全相同的值。为了使这些规定变得有用,所有的包都必须这样

设计 以便在用户这样做的时候它们将能够有效地工作。

配置是如何进行的
每个GNU发布版本都应该还有一个名为configure的shell脚本。你需要把 你希望在那种机器和系统上编

译程序作为参数告诉这个脚本。

脚本configure必须记录配置信息以便它们可以影响编译工作。

这样做的一种方式是把一个诸如`config.h'的标准名字和为选定的系统匹配的 正确配置文件连接起来

。如果你使用了这种技术,发布版本中就不应该包含名为 `config.h'的文件。这样做是为了保证用户

在配置程序之前不能够创建它。

configure可以做的另一件事情是编辑Makefile。如果你这样做了,发布 版本中就不能包含名为

`Makefile'的文件。用`Makefile.in'来代替 它,并且`Makefile.in'为configure的编辑提供了输入。

同样, 这样做是为了保证用户在配置程序之前不能创建它。

如果configure生成了`Makefile',那么`Makefile' 就应该包含一个名为`Makefile'的目标,这个目标

将重新运行configure 以与获取上一次配置相同的配置信息。由configure读取的文件,应该作为 依赖

性文件而在`Makefile'中被列出。

所有由configure脚本生成的文件在它们的第一行都应该包含一条注释 以说明它们是由configure自动

生成的。这样做是为了确保用户不会试图手工 修改它们。

脚本configure应该写入一个名为`config.status'的文件,该文件 说明了在程序的最后一次配置中给

出了那些配置选项。该文件应该是一个shell脚本,如果运行它, 将重新生成相同的配置。

脚本configure应该接受形式为`--srcdir=dirname' 的选项以指明在那个目录中可以找到源代码(如果

源代码不在当前目录中)。这使得可以在 实际代码目录没有被修改的情况下,在分离的中创建程序成

为可能。

如果用户没有给出`--srcdir',那么configure将在`.' 和`..'中寻找源文件。如果它在上述地方之一

发现源文件,它就应该在那里使用它们。 否则,它应该报告它没有找到源文件,并且以非零状态退出

通常,支持`--srcdir'的简单方式是通过编辑被放到Makefile中的一个 VPATH的定义。可能有一些规则

需要被显式地引用以指明源代码目录。为了达到这个 目的,configure可以把一个名为srcdir的变量添

加到Makefile中, 该变量的值就是给定的目录。

脚本configure还应该提供一个可以指明程序是究竟为那种系统而创建的选项。 这个选项看起来应该象

cpu-company-system
例如,一个Sun 3可能是`m68k-sun-sunos4.1'。

脚本configure需要能够解释所有对机器的似是而非的描述方式。因此, `sun3-sunos4.1'应该是有效

的别名。`sun3-bsd4.2'也是如此, 因为SunOS是基于BSD的并且没有其它的BSD系统被用于Sun。对于许

多程序来说, 因为Ultrix和BSD之间的区别很少被注意到,所以`vax-dec-ultrix'将是 `vax-dec-bsd'

的一个别名。但少数程序可能需要区分它们。

这里有一个被称为`config.sub'的shell脚本,你可以把它作为一个子程序使用 以检查系统类型并且对

别名进行规范化。

允许出现其它选项以指明关于机器的软件或者硬件的更多细节:

`--with-package'
包package将被安装,所以把本包配置成与package一同工作。
package可能的取值包括`x'、`gnu-as'(或者 `gas')、`gnu-ld'、`gnu-libc'和`gdb'。

`--nfp'
目标机器没有浮点数处理器。
 

`--gas'
目标机器的汇编器是GAS,GNU的汇编器。该选项已经过时了;用`--with-gnu-as'来代替。
 

`--x'
目标机器已经安装了X Window系统。该选项已经过时了;用`--with-x' instead来代替。
所有的configure脚本都应该接受所有这些“细节”选项,而不论它们是否会对手头的特定 包产生影响

。特别地,它们应该接受任何以`--with-'开头的选项。这样做是因为这使得用户 可以用同一组选项配

置整个GNU源代码树。

作为编译的一部分的包可能支持交叉编译(cross-compliation)。在这种情况下,程序的主机和目标

机器 可能是不同的。configure通常把指明的系统类型当作主机和目标机器,因此将创建与运行它 的

机器类型相同的机器上运行的程序。

创建交叉编译器(cross-compiler)、交叉汇编器(cross-assembler)、或者你自己的程序,通过在

运行 configure时给出选项`--host=hosttype'来完成。它在不影响 目标机器的情况下指明了主机名。

hosttype的语法与前面所说的一样。

因为为互操作(cross-operation)配置整个操作系统是一件没有意义的事,对互操作来说没有意义的

程序 就不必接受选项`--host'。

有些程序自动地配置它们自己。如果你的程序被设置成这样,你的configure脚本只需要简单地 忽略它

的大部分参数就行了。

使用C以外的语言
使用C以外的语言就好像使用非标准特征:它将为用户带来麻烦。即使GCC能够支持其它语言, 用户也

可能因为不得不安装其它语言的编译器以创建你的程序而感到不便。所以请使用C语言。

这条规则有三个例外:

如果有些程序包括了特殊语言的解释器,那么就可以使用这种语言。
因此,GNU Emacs包含用Emacs Lisp写的代码就没有问题,因为GNU Emacs包含了Lisp解释器。

如果一个工具就是为了某种语言而编写的,那么就可以使用那种语言。
这是因为那些需要创建这个工具的人必然是那些已经安装了其它语言的人。

如果一个应用程序没有被极端广泛地关注,那么应用程序的安装不太方面就不是特别重要。

格式化你的源代码
把作为C函数的开头的左花括号放到第零列是十分重要的,并且避免把任何其它的左花括号、左括号 或

者左方括号放到第零列。有些工具通过寻找在第零列的左花括号来寻找C函数的起点。这些工具将不能

处理那些不按照这种方式排版的代码。

对于函数定义来说,把函数名的起始字符放到第零列也同样重要。这帮助任何寻找函数定义,并且 可

能有助于帮助某些工具识别它们。因此,正确的格式应该是:

static char * concat (s1, s2)        /* Name starts in column zero here */      char *s1,

*s2; {                     /* Open brace in column zero here */   ... }
或者,如果你希望使用标准C,定义的格式是:

static char * concat (char *s1, char *s2) {   ... }
在标准C中,如果参数不能够被美观地放在一行中,按照下面的方式把它们分开:

int lots_of_args (int an_integer, long a_long, short a_short,               double

a_double, float a_float) ...
对于函数体,我们希望它按照如下方式排版:

if (x < foo (y, z))   haha = bar[4] + 5; else   {     while (z)       {         haha +=

foo (z, z);         z--;       }     return ++x + bar ();   }
我们发现如果在左括号之前以及逗号之后添加空格将使程序更加容易阅读。尤其是在 逗号之后添加空

格。

当我们把一个表达式分成多行的时候,在操作符之前而不是之后分割。下面是正确的方式:

if (foo_this_is_long && bar > win (x, y, z)     && remaining_condition)
尽力避免让两个不同优先级的操作符出现在相同的对齐方式中。例如,不要象下面那样写:

mode = (inmode[j] == VOIDmode         || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE

(inmode[j])         ? outmode[j] : inmode[j]);
应该附加额外的括号以使得文本缩进可以表示出这种嵌套:

mode = ((inmode[j] == VOIDmode          || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE

(inmode[j])))         ? outmode[j] : inmode[j]);
插入额外的括号以使得Emacs可以正确地对齐它们。例如,如果你手工完成缩进工作, 那么它们看起来

不错,但Emacs将把它们混在一起:

v = rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000     + rup->ru_stime.tv_sec*1000

+ rup->ru_stime.tv_usec/1000;
但添加一组括号解决了这个问题:

v = (rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000      + rup-

>ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000);
按照如下方式排版do-while语句:

do   {     a = foo (a);   } while (a > 0);
请按照逻辑关系(而不是在函数中)使用走纸字符(control-L)以把程序划分成页。 页有多长并不重

要,因为它们不必被放在一个打印的页中。走纸字符应该单独地出现在一行中。

为你的工作写注释
每个程序都应该以一段简短地、说明其功能的注释开头。 例如:`fmt - filter for simple filling

of text'.

请为每个函数书写注释以说明函数做了些什么,需要哪些种类的参数,参数可能值的含义 以及用途。

如果按照常见的方式使用C语言类型,就没有必要逐字重写C参数声明的含义。如果 它使用了任何非标

准的东西(例如,一个类型为char *的参数实际上给出了 一个字符串的第二个字符,而不是第一个字

符,的地址),或者是可能导致函数不能工作的 任何可能的值(例如,不能保证正确处理一个包含了

新行的字符串),请确认对它们进行了说明。

如果存在重要的返回值,也需要对其进行解释。

请在你的注释之后添加两个空格,以便Emacs句子命名进行处理。还有,请书写完整的句子 并且使头一

个单词以大写字母开头。如果小写字母组成的标识符出现在句子的开头,不要把它 变成大写的!修改

拼写就构成了不同的标识符。如果你不希望句子以小写字母开头,可以写下 不同的句子(例如,“The

identifier lower-case is ...”)。

如果你使用参数名来说明参数值,关于函数的注释就会更清晰。变量名本身应该是小写的, 但在你说

到它的值而不是变量本身的时候就使用大写字母。因此,“the inode number node_num” 比“an

inode”要好。

通常在函数之前的注释中没有必要重新提到函数的名字,因为读者可以自己看到它。 一种可能的例外

是:注释太长了,以至于函数本身被挤出了屏幕底端之外。

对于每个静态变量,也象下面那样应该提供注释:

/* Nonzero means truncate lines in the display;    zero means continue them.  */  int

truncate_lines;
除非`#endif'是一个没有嵌套而且很短(只有几行)的条件,每个 `#endif'都应该含有一个注释。注

释应该说明它所结束的条件,包括它的含义。 `#else'应该含有一个说明条件与随后代码的含义的注释

。例如:

#ifdef foo   ... #else /* not foo */   ... #endif /* not foo */
但相反,按照如下方式为`#ifndef'写注释:

#ifndef foo   ... #else /* foo */   ... #endif /* foo */

清晰地使用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
在一个交互式程

抱歉!评论已关闭.