什么是 GNU

作者:Arnold Robbins

是什么让一个 GNU 程序成为 GNU 程序?是什么让 GNU 软件比其他(自由或非自由)软件“更好”?最明显的区别是 GNU 通用公共许可证 (GPL),它描述了 GNU 软件的发布条款。但这通常不是您听到人们说“获取 xyz 的 GNU 版本,它好得多”的原因。GNU 软件通常比标准的 Unix 版本更健壮,性能更好。我们将看看其中的一些原因,以及描述 GNU 软件设计原则的文档。

GNU 编码标准》描述了如何为 GNU 项目编写软件。它涵盖了一系列主题。截至撰写本文时,相关章节尚未分组在一起,因此我们将按主题查看这些章节,而不是按它们出现的顺序。

您可以在 Autoconf 发行版中找到《GNU 编码标准》,目前是 autoconf-2.3.tar.gz,从您最近的 GNU 镜像站点获取。ASCII 副本 (standards.txt) 也应该可以从您最近的 GNU 镜像站点作为独立文件获取。

知识产权

讨论的第一个问题与知识所有权有关。如果您要编写一个重新实现 Unix 实用程序的 GNU 程序,不要查看 Unix 源代码!(如今,源代码许可证越来越难获得,因此这个问题比 10 年前要小得多。)另一个问题与版权转让有关。如果您要编写或参与 GNU 程序,您必须声明您的作品属于公共领域,或将其版权转让给 FSF。(小的改动不需要这样做,因此如果您想提交错误修复,请不要因此而害怕。另一方面,如果您增强 GNU [cw]find[ecw] 以使其可以读取 cpio 存档磁带,您可能需要办理一些手续。即使这样通常也很轻松。)

当然,您可以从头开始编写程序,在 GPL 下发布它,并保留版权。您也可以对 FSF 拥有版权的程序进行自己的更改,并在 GPL 下将您的版本与 FSF 的版本分开分发。仅当您希望将您的更改合并回 GNU 程序的主发行版时,才需要将版权转让给 FSF。

程序设计

许多章节提供了关于程序设计的通用建议。四个主要问题是兼容性(与标准和 Unix),使用哪种语言编写,是否依赖其他程序的非标准功能(总而言之,“不要”),以及“可移植性”的含义。

与 ANSI、POSIX 和 Berkeley Unix 的兼容性是一个重要的目标。但它不是首要目标。总体思路是提供所有必要的功能,并使用命令行开关来提供严格的 ANSI 或 POSIX 模式。

C 是编写 GNU 软件的首选语言,因为它是最常用的语言。在 Unix 世界中,ANSI C 现在才变得普遍(可悲但事实如此),因此 K&R C 仍然是最广泛使用的可移植方言。但这正在迅速改变,C++ 变得越来越普遍。一个广泛使用的用 C++ 编写的 GNU 软件包是 groff (GNU troff)。由于 GCC 支持 C++,我的经验是安装 groff 并不困难。

标准声明可移植性有点转移注意力。GNU 实用程序最终旨在在带有 GNU C 库的 GNU 内核上运行。但由于内核尚未完成,并且用户在非 GNU 系统上使用 GNU 工具,因此可移植性是可取的,但不是最重要的。该标准建议使用 Autoconf(我希望有一天能写一篇关于它的专栏文章)来实现不同 Unix 系统之间的可移植性。

程序行为

下一组章节提供了关于程序行为的通用建议。我们稍后将回头详细查看其中一个章节。这些章节侧重于如何设计您的程序、错误消息应如何格式化、如何编写库(使其可重入)以及命令行界面的标准。

错误消息格式化很重要,因为一些工具,特别是 Emacs,使用错误消息来帮助您直接转到发生错误的源文件或数据文件中的点。

GNU 实用程序应使用名为 getopt_long 的函数来处理命令行。此函数为传统的 Unix 风格选项 (gawk -F: ...) 和 GNU 风格长选项 (gawk --field-separator=: ...) 提供命令行选项解析。所有程序都应提供 --help--version 选项,并且当一个长名称在一个程序中使用时,它应该在其他 GNU 程序中以相同的方式使用。为此,有一个相当详尽的当前 GNU 程序使用的长选项列表。

编写 C 代码

手册中最实质性的部分描述了如何编写 C 代码,涵盖了诸如格式化代码、注释、如何干净地使用 C、如何命名您的函数和变量以及如何声明或不声明您希望使用的标准系统函数之类的内容。

代码格式化是一个宗教问题;许多人有他们喜欢的不同风格。我个人不喜欢 FSF 的风格,如果您看看我维护的 gawk,您会看到它是以标准的 K&R 风格格式化的。但这是 gawk 中与编码标准的这一部分唯一的不同之处(其他不同之处将在今年即将到来的 gawk 3.0 中消失)。

尽管如此,虽然我不喜欢 FSF 的风格,但我认为,在修改其他程序时,坚持已经使用的编码风格至关重要。拥有一致的编码风格比您选择哪种编码风格更重要。

我认为关于 C 编码的章节中重要的一点是,这些建议对于任何 C 编码都很有用,而不仅仅是如果您碰巧在 GNU 项目上工作。因此,如果您只是在学习 C,或者即使您已经使用 C(或 C++)一段时间了,我也会向您推荐这些章节,因为它们概括了多年的经验。

文档化程序

有两章介绍了为您的程序编写文档。首选方法是使用 Texinfo 编写手册,这在之前的专栏文章(第 6 期,1994 年 10 月)中讨论过。这里有一些关于编写手册的很好的建议。而且,如前所述,Texinfo 是一种编写文档的愉快语言。

如何发布

最后,有三章专门介绍发布机制。这些章节讨论了 Makefile 的使用约定、配置应如何工作以及关于发布应如何工作的其他一般性问题。

这些章节与 Autoconf 手册一起,提供了打包程序和制作最终发布的 tar] 文件所需的信息。

是什么让 GNU 程序更好?

我们现在来看看题为《所有程序的程序行为》的章节。本章提供了使 GNU 程序优于其 Unix 对应程序的软件设计原则。我们将引用本章的选定部分,并举例说明这些原则在哪些方面取得了回报。

通过动态分配所有数据结构,避免对任何数据结构(包括文件名、行、文件和符号)的长度或数量设置任意限制。在大多数 Unix 实用程序中,“长行会被静默截断”。这在 GNU 实用程序中是不可接受的。

这也许是 GNU 软件设计中最重要的规则,“没有任意限制”。所有 GNU 实用程序都应该能够管理任意数量的数据。

虽然这使程序员的工作更加困难,但它使用户的体验更好。我有一个 gawk 用户,他在超过 650,000 个文件(不,这不是笔误)上运行 awk 程序来收集统计信息。gawk 增长到超过 192 兆字节的数据空间,并且程序运行大约七个 CPU 小时。如果使用其他 awk 实现,他根本无法运行他的程序。

读取文件的实用程序不应丢弃 NUL 字符或任何其他非打印字符(包括代码高于 0177 的字符)。唯一合理的例外是专门用于连接某些类型无法处理这些字符的打印机的实用程序。

众所周知,Emacs 可以编辑任何任意文件,包括包含二进制数据的文件!

检查每个系统调用的错误返回,除非您知道您希望忽略错误。在每个由失败的系统调用产生的错误消息中,都包含系统错误文本(来自 perror 或等效项),以及文件名(如果有)和实用程序的名称。仅仅“无法打开 foo.c”或“stat 失败”是不够的。

检查每个系统调用可以提高健壮性。这是另一种情况,程序员的工作更加困难,但用户的体验更好。详细说明究竟出了什么问题的错误消息使查找和解决任何问题变得更加容易。

检查对 malloc 或 realloc 的每次调用,看看它是否返回零。即使您要缩小块,也要检查 realloc;在将块大小四舍五入为 2 的幂的系统中,如果您请求更少的空间,realloc 可能会获得不同的块。

在 Unix 中,如果 realloc 返回零,则可能会破坏存储块。GNU realloc 没有此错误:如果它失败,则原始块保持不变。您可以放心地假设该错误已修复。如果您希望在 Unix 上运行您的程序,并希望在这种情况下避免丢失,您可以使用 GNU malloc

您必须预期 free 会更改已释放的块的内容。您要从块中获取的任何内容,都必须在调用 free 之前获取。

在短短三个段落中,Richard Stallman 提炼了使用 malloc 进行动态内存管理的重要原则。“没有任意限制”原则和动态内存的使用使 GNU 程序比其 Unix 对应程序更健壮、功能更强大。

使用 getopt_long 解码参数,除非参数语法使其不合理。

前面提到了长选项。它们的使用旨在使 GNU 程序比 Unix 版本更易于使用且更一致。getopt_long 函数是一个很好的函数;它为您提供参数解析可能需要的所有灵活性和功能。作为一个简单而明显的例子,--verbose所有 GNU 程序中的拼写完全相同。将其与 -v-V-d 等进行对比。

最后,我们将引用前面章节中的一段话,该章节讨论了如何以不同于 Unix 程序编写方式的方式编写程序。

例如,Unix 实用程序通常经过优化以最大限度地减少内存使用;如果您转而追求速度,您的程序将会非常不同。您可以将整个输入文件保留在核心中并在那里扫描它,而不是使用 stdio。使用比 Unix 程序更新发现的更智能的算法。消除临时文件的使用。一次完成而不是两次完成(我们在汇编程序中就是这样做的)。

或者,相反,强调简单性而不是速度。对于某些应用程序,当今计算机的速度使更简单的算法足够用。或者追求通用性。例如,Unix 程序通常具有静态表或固定大小的字符串,这会导致任意限制;而是使用动态分配。确保您的程序处理输入文件中的 NUL 和其他奇怪字符。添加一种编程语言以实现可扩展性,并用该语言编写程序的一部分。

算法可以带来差异的一个极好的例子是 GNU diff。我的计算机之前的化身是 AT&T 3B1;一个带有 MC68010 处理器、惊人的两兆字节内存和 80 兆字节 MFM 磁盘的系统。

我过去(现在也)对 gawk 的手册进行了大量编辑,该文件目前超过 17,000 行(尽管当时只有 10,000 行左右)。我过去经常使用 diff -c 来查看我的更改。在这个缓慢的系统上,切换到 GNU diff 使上下文差异出现所需的时间产生了非常显着的变化。这种差异几乎完全归因于 GNU diff 使用的更好的算法。

总结

如果您希望开发新的 GNU 软件、增强现有的 GNU 软件,或者只是希望学习如何成为一名更好的程序员,《GNU 编码标准》是一份值得阅读的文档。它所支持的原则和技术使 GNU 软件成为 Unix 社区的首选。

结语

如前所述,已发布的标准版本以相当随意的顺序涵盖其主题。由于撰写本专栏文章,我自愿将其重新组织成几个相关章节。这个新版本可能会在您阅读本文时发布;请密切关注您最近的 GNU 镜像站点。

Arnold Robbins 是一位专业程序员和半专业作家。自 1987 年以来,他一直在为 GNU 项目做志愿者工作,自 1981 年以来一直从事 UNIX 和类 UNIX 系统的工作。问题和/或评论可以通过电子邮件发送至 arnold@gnu.ai.mit.edu

加载 Disqus 评论