zoukankan      html  css  js  c++  java
  • GNU编码标准

    GNU编码标准


    引用私有程序

    不要在任何情况下,为你在的GNU中的工作或者在工作中引用Unix的源代码(或者任何其它私有程序)。如果你对一个Unix程序内容有一些模糊的记忆,这并不因为着你绝对写程序来模仿它,但请试图在内部使用不同的代码行来组织它,因为这将使你工作的结果在细节上与Unix版本有所不同。例如,Unix工具通常进行了优化以使用最少的内存;如果你更希望提高速度,你的程序将会有很大的不同。你可以在内核中保存整个输入文件并且在内存中扫描而不是使用stdio

    使用比Unix程序更新的、更明智的算法。不使用暂时文件。在一遍扫描而不是两遍扫描中完成任务(我在assembler(汇编器)中这样做了)。或者相反,强调简单性而不是速度。对于一些应用程序来说,今天的计算机只要使用简单的算法就够了。或者注重一般性。例如,Unix程序通常使用静态的表格和固定大小的字符串,这导致了不可改变的限制;用动态分配来代替。确认你的程序处理了输入文件为空和其它滑稽的情况。为增加扩展性而增加一种程序语言并且用那种语言完成程序的一个部分。或者把程序的一部分修改成独立的库。或者用一个简单的废物收集器而不是在释放内存的时候精确地进行跟踪,

    或者使用诸如obstacks这样的新的GNU工具。


    接受他人的奉献

    如果其他人发给你一段添加到你正在编写程序中的代码,我们需要准许使用它的法律文书——我们将需要从你那里取得同样的法律文书。程序的每个重要的贡献者都必须签署

    某种法律文书以使得我们可以给程序一个清晰的标题。仅有主要作者是不够的。所以,在把来自于他人的任何共享添加到程序中之前,告诉我们以便我们可以做出安排以获取文书。在你实际地使用贡献之前,请等待直到我们告诉你我们已经收到了签署的文书。这即适用于你发行程序之前也适用于发行之后。如果你收到了一个修正bug的补丁,并且它们做了主要的修改,我们就需要为他提供法律文书。

    你不需要为这里或者那里的少数几行修改提供文书,因为对于达到版权目的没有意义。还有,如果你从建议中获得的仅仅是一些想法,而不是你实际上使用的代码,你也不需要文书。例如,如果你写了一个程序的不同解决方案,你并不需要获得许可文书。

    我知道这是十分麻烦的;它对我们来说也十分麻烦。但如果你不等待,你就可能误入歧途,如果这个贡献者的雇主不肯签署弃权声明怎么办?你可能不得不再次把代码剔除出来!最糟糕的情况是如果你忘记告诉我们其它的贡献者,我们可能会因此而窘迫地出现在法庭上。


    修改日志

    为每个目录维护一个修改日志,以记述对这个目录下源文件的修改。这样做的目的是使得在将来寻找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规范向上兼容。当这些标准发生冲突的时候,为每个标准提供兼容模式是有用的。标准CPOSIX禁止进行任何形式的扩展。自由地进行你的扩展,并且把选‘—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) $< -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 grepln mkdir mv pwd rm rmdir sed test touch

    坚持使用这些程序通常支持的选项。例如,因为许多系统不支持‘mkdir –p’,尽管它可能有些方便,但不要使用它。

    为创建和安装而提供的Makefile规则还可以使用编译器和相关的程序,但应该通过make变量以便用户对它们进行替换。下面是一些我们所说的相关的程序:

    ar bison cc flex install ld lexmake makeinfo ranlib texi2dvi yacc

    在你使用ranlib的时候,你应该测试它是否存在,并且仅仅在它存在的情况下运行它,以使得发布版本在那些没有ranlib的系统中也能够工作。如果你使用了符号连接,你应该为没有符号连接的系统实现一个替代手段。你可以在只打算用于特定系统的Makefile的部分(或者脚本)中使用你能够确认在那些系统上存在的工具。


    为用户提供的标准目标

    所有的GNU程序应该在它们的Makefile中含有下列目标:

    ‘all’

    编译整个程序。它应该是缺省目标。这个目标不需要重新创建任何文档文件;Info文件被包含在发布版本中,同时,只有在用户明确地要求创建DVI文件的时候才创建DVI文件。

    ‘install’

    编译程序并且把可执行文件、库文件等文件复制到它们在实际应用中应该存在的位置。如果存在一个可以检测程序是否被正确地安装了的简单测试,本目标将首先运行这个测试。如果文件的安装目录不存在,该命令将创建这样的目录。它们包括由变量prefixexec_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文件等等。然而有一个例外:即使‘onfigure’以通过使用Makefile中的规则重新创建,‘ake realclean’也不会删除‘onfigure’更一般地说,‘ake realclean’不会删除为了运行‘onfigure’而存在的任何东西,并且随后开始创建程序。

    ‘AGS’

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

    ‘nfo’

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

    info: foo.infofoo.info: foo.texi chap1.texi chap2.texi $(MAKEINFO) $(srcdir)/foo.texi

    你必须在Makefile中定义变量MAKEINFO。它应该运行程序makeinfo,该程序是Texinfo发布版本的一部分。

    ‘vi’

    为所有Xinfo文档VI文件,比如:

    dvi: foo.dvifoo.dvi: foo.texi chap1.texi chap2.texi $(TEXI2DVI) $(srcdir)/foo.texi

    你必须在Makefile中定义变量TEXI2DVI。它应该运行程序texi2dvi,该程序也是Texinfo

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

    ‘ist’

    为本程序创建一个发布版本tar文件。该tar文件将被设置以使得在tar文件中的文件名以子目录名开头,这个子目录名是包用于发布的名字。这个名字可以包含版本号。例如,GCC版本1.40的发布tar文件将被解包到名为’gcc-1.40’的子目录中。完成该任务的最简单方式是以适当的名称创建一个子目录,使用ln或者cp把正确的文件安装到该目录中,而后tar这个子目录。目标dist应该显式地依赖于发布版本中所有的非源文件,以确保它们在发布版本中都不是过时的。参见制作发布包

    ‘heck’

    (如果有的话)执行自检测。用户必须在运行测试之前,但不必在安装程序之前创建程序;你应该写下自检测以便它们在程序创建之后而没有被安装之前进行工作。对于那些适用于以下的目标的程序,建议你按照常用的名字提供它们。

    ‘nstallcheck’

    (如果有的话)执行安装监测。用户必须在运行该检测之前创建并且安装程序。你不应该假定’(bindir)’出现在搜索路径中。

    ‘nstalldirs’

    添加一个名为‘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)引用它。在这种方式下,文件管理工具:lnrmmv等等并不需要通过变量引用,这是因为用户不需要用其它程序来替代它们。每个程序名变量都应该有一个对应的变量以便为程序提供选项。把’FLAGS’附加到程序名变量名的后面就是选项变量名--例如,BISONFLAGS。(名字CFLAGS是这项规则的一个例外,但因为它是标准的而保留了它。)在任何运行预处理器的编译命令中使用CPPFLAGS,在任何进行连接的编译命令和任何对ld的直接使用中使用LDFLAGS

    如果存在一些为了正确地编译某些文件而必须使用的C编译器选项,不要把它们包括在CFLAGS中。用户希望能够自由地指明CFLAGS的值。替代的方式是:通过在编译命令行中显式地给出这些必要的选项或者通过定义一条隐含规则,从而以独立于CFLAGS的方式把选项传递给C编译器。

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

    把选项‘-g’包括在CFLAGS中,因为它对于正确的编译来说并不是必要的。你可以认为它仅仅是关于缺省值的一个建议。如果包被设置成在缺省的状态下由GCC编译,那么你可能还需要把‘-O’包括在CFLAGS的缺省值之中。

    CFLAGS放在编译命令行的最后,就是在其它包含了编译选项的变量之后,以便于用户使用CFLAGS来覆盖其它的选项。每个Makefile都应该定义变量INSTALL,它是把一个文件安装到系统中的基本命令。每个Makefile还应该定义变量INSTALL_PROGRAMINSTALL_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命令就不应该试图使用oldincludedirMakefile命令应该放弃对头文件的第二个安装。除非头文件来自于同一个包,包不应该替换已经存在的头文件。因此,如果你的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/localexec_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发布版本都应该还有一个名为configureshell脚本。你需要把你希望在那种机器和系统上编译程序作为参数告诉这个脚本。脚本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。对于许多程序来说,因为UltrixBSD之间的区别很少被注意到,所以‘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’

    目标机器的汇编器是GASGNU的汇编器。该选项已经过时了;用’--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中,如果参数不能够被美观地放在一行中,按照下面的方式把它们分开:

    intlots_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 ifthen部分代码,也可以在

    花括号中像下面那样把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.96tar文件包的名字是’foo-69.96.tar’。它应该被解包到名为

    ‘foo-69.96’的子目录中。对程序的创建和安装不应该修改发布版本中的任何文件。这意味着以任何方式作为程序的正式部分的所有文件都必须被分成源文件和非源文件两类。源文件由人手工编写并且不会被自动改变;非源文件则在Makefile的控制下由程序从源文件生成。自然地,所有源文件必须出现在发布版本中。只有在非源文件不是过时的并且与机器是无关的情况下,从而在创建发布版本时将不会需要修改它们,才能把非源文件包含在发布版本中。我们一般把由BisonLexTeXMakeinfo生成的非源文件包括进去;这有助于避免在我们的发布版本中引入不必要的依赖性,以使得用户可以安装他们需要安装的包。

    永远不要把实际上可能在程序的创建和安装中被修改的非源文件包含在发布版本中。所以如果你发布非源文件,在你作新的发布时,总是要确认它们没有过时。确保从发布包中解开的目录(以及所有的子目录)对于所有人来说都是可写的(八进制模式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’的副本。同样地,如果你的程序使用了诸如regexgetoptobstack或者termcap之类的小GNU软件包,把它们包括在发布文件中。把它们排除在外将使发布文件小一些,其代价是给那些不知道需要哪些额外文件的用户带来不便。

  • 相关阅读:
    桥接模式新解:客户端服务器模式
    laravel-admin 自定义导出表单
    PHP实现git部署的方法,可以学学!
    PHP的安全性问题,你能说得上几个?
    nginx的四个基本功能
    laravel orm
    laravel-admin列表排序在使用了$grid->model()->latest()后$grid其它加上sortable()可排序的列在排序时不起作用
    Laravel 5.4: 特殊字段太长报错 420000 字段太长
    关于在phpStudy环境下,windows cmd中 php不是内部命令问题
    phpstudy安装好之后mysql无法启动(亲测可行)
  • 原文地址:https://www.cnblogs.com/zhihaowang/p/10128727.html
Copyright © 2011-2022 走看看