Linux 编程提示

作者:Michael K. Johnson
GNU C 库简介

在本专栏中,我将探讨 GNU C 库。自由软件基金会 (FSF) 编写了一份出色的参考手册,该手册以电子形式提供,可以打印或在线阅读,但我认为一个简介将有助于一些人入门。

GNU C 库不仅仅是对标准 C 库的重新实现;虽然它具有标准 C 库的所有功能,但它也具有更多有趣和有用的功能。不幸的是,在您的程序中使用所有这些功能不一定是好主意。

GNU 与标准

FSF 用来避免不高兴的商业供应商提起版权侵权诉讼的一种方法是从 GNU 版本的程序中删除限制和任意限制。例如,标准版本的程序可能限制为处理少于 4096 个字符的行,而 GNU 版本很可能处理内存可以容纳的任何长度的行。

他们在他们的 C 库版本中也遵循了相同的理念:只要库仍然兼容,为什么不进行改进呢?因此,当调用类似 printf("%s", NULL) 的内容时,大多数标准 C 库都包含一个会导致段错误 (segmentation violation) 的 printf(),而 GNU C 库则打印 (null)。这不是用于打印 (null) 的功能,而是一种调试辅助工具,它允许程序员更轻松地查找和更正错误代码,而无需检查由段错误引起的核心文件。

在保持 POSIX (可移植操作系统接口) 兼容性的同时,FSF 显着扩展了 C 库,使其在此过程中更加有用。不幸的是,当您使用这些扩展时,您的程序在其他平台上的可移植性会降低。为了使程序普遍有用,GNU C 库应移植到您的程序可能对其有用的任何平台。

另一方面,编写需要 GNU C 库的优秀软件可能会鼓励 GNU C 库的进一步传播。它也可能使您的程序运行得更好,因为程序构建在其上的库越好,程序可能就越好;并且一些更高级别的函数可能允许您编写更简单、更易于维护的代码。您可以减少克服库限制所花费的精力。正如资深程序员所知,有缺陷的库会浪费程序员的大量时间。由于 GNU C 库以作为标准 C 库的良好实现而闻名,并具有有用的扩展,因此通过鼓励 GNU C 库的传播,您可能会为您的程序员同行们提供帮助。

鼓励 GNU C 库传播的另一个原因是它是一个自由软件。当你不明白库函数调用在做什么时,能够阅读库源代码会非常有帮助。

linux C 库几乎完全基于 GNU C 库,并且最终可能会与 GNU C 库合并。这并不意味着在 linux 下编写程序需要或鼓励编写不可移植的程序。GCC (GNU Compiler Collection) 的 -ansi 开关强制执行相当严格的 ANSI (美国国家标准协会) 合规性 (1),并且默认情况下屏蔽对头文件中所有 GNU 扩展的引用,以便您可以确保您的程序完全可移植。《GNU C 库参考手册》中的 1.3.4 节“功能测试宏”解释了在使用 GNU C 库时如何选择要包含的功能。

如果您编写的程序基于诸如 W. Richard Stevens 的《Unix 环境高级编程》、Kernighan 和 Ritchie 的《C 程序设计语言》、Donald Lewine 的《POSIX 程序员指南》以及其他此类标准参考书,那么您的代码应该可以移植到许多操作系统以及 linux。但是,对于 linux,您可以选择使用 GNU 特定的库例程,以及在其他平台上推广 GNU C 库的使用。

核心内容

在本专栏的剩余部分,我们将抛开这些哲学上的漫谈,并假设您已选择充分利用 GNU C 库,超越 ANSI 标准,并且您想要了解其扩展,以便您知道有哪些功能可以使用。我将浏览参考手册,指出并简要解释 GNU C 库的许多有用增强功能。这不是对 GNU C 库的连贯讨论,而是一个想要使用 GNU C 库进行严肃编程的人应该了解的扩展列表。这样,他们就可以决定是否使用这些功能,而不是因为无知而被谴责而忽略它们……

如果您发现这些功能值得使用,请在—GNU C 库参考手册中查找它们。不要仅仅根据我在这里的描述来尝试使用它们 - 这些描述只是为了引起您的兴趣。请参考参考手册。

错误报告

通常在 main() 中检查 argv 以找出用于调用程序的名称。但是,为了使错误报告机制正常工作,指向 argv[0] 的变量必须是全局变量(至少在程序的某些部分中)或从一个函数传递到另一个函数并用作错误处理函数的参数——这两种方法都可能变得相当混乱。

GNU C 库提供了两个变量,这两个变量在调用 main() 之前自动初始化,从而解决了这个问题。char *program_invocation_name 包含在 argv[0] 中找到的名称的精确副本,而 char *program_invocation_short_name 包含一个副本,其中所有前导目录名称都被剥离。因此,如果 program_invocation_name 包含 /usr/bin/foo,则 program_invocation_short_name 包含 foo

有了这两个变量,错误处理函数变得更加简单和通用。即使没有这些预先提供的变量,也可以创建清晰的错误处理函数,但这需要您在程序初始化期间(可能从 main())初始化错误处理函数。如果您假设 GNU C 库可用,您可以直接访问这些变量,从而减少程序员出错的可能性。

内存分配

GNU C 库包含内置的堆一致性检查,这意味着它可以检查程序是否违反了访问动态分配内存的一些规则。通过在调用任何内存分配函数之前调用 mcheck() 函数,您可以要求偶尔进行一些一致性检查,并且如果存在任何不一致,则调用错误函数。

您还可以定义在调用 malloc()realloc()free() 之前直接调用的函数,以检查错误。mcheck() 是通过使用这些钩子实现的,但是即使您正在使用 mcheck(),您仍然可以使用这些钩子,因为这些函数是“链接的”——您只需要遵循参考手册中给出的规则和示例即可使其正常工作。

提供了一个 mstats() 函数,该函数获取内存分配统计信息,包括

  • malloc()(等)管理的字节总数,包括已从操作系统分配但未由 malloc() 分配给您的程序的内存。

  • 实际分配给您的程序的字节数。

  • 已从操作系统分配但未使用的“块”的数量。

  • 实际使用的“块”的数量。

  • 已由 malloc() 从操作系统分配但当前未分配给您的程序的空闲字节数。

提供了一个名为 obstacks 的动态堆栈分配工具,对于某些事情来说,它可能比 malloc 更有效。Obstacks 有一些限制,但它们是作为宏实现的,并且对于小的、重复的分配非常快。与 malloc() 相比,它们每个小块的空间开销也更低。

Obstacks 的构建方式与 malloc() 构建在系统调用 brk() 上的方式非常相似。

还提供了一个重定位分配器。这是一种内存分配器,它提供可以在后台随时移动的内存块,因此通过“句柄”引用这些内存块,该“句柄”在内存移动时会更新。

使用重定位内存进行编程可能会稍微麻烦一些,因为您必须使用例如 char ** 而不是 char *,但是如果您的程序定期以或多或少随机的方式分配和释放内存,则重定位分配器可以提供显着的内存节省。

输入和输出

由于标准 C 库中没有真正好的函数用于读取行,因此 GNU C 库提供了一些额外的函数,这些函数不是完全兼容的,但效果更好。getline() 可以安全地读取与内存容量一样长的字符串。getdlim()getline() 的通用版本,它可以获取文本,直到再次到达某个分隔符,而对行可以有多长没有任意限制。在这些函数中,内存是从函数内部分配的,而不是函数要求您传递内存。当您完成操作后,您需要释放此内存。

snprintf()asprintf()obstack_printf() 提供了安全的格式化字符串 I/O,其中第一个是 sprintf() 的一个版本,它知道它必须写入的字符串有多长;另外两个动态分配它们需要的任何空间,就像 getline()getdlim() 一样。

GNU C 库提供了用于自定义 printf() 的函数。例如,您可以为标准 printf() 定义 %q 格式,并使其执行您想要的任何操作。如果您希望能够轻松地打印出应用程序中的结构,只需为它们进行 printf() 转换,并将指向结构的指针传递到 printf() 中。如果 %q 是您的通用结构打印转换,并且 struct foo 已被指定为结构编号 1,您可以使其能够编写:printf("%1q\n", &foo); 并为您打印出 foo 的内容。

scanf() 已兼容地扩展,以便它可以选择性地分配字符串存储本身,因此,例如,您不必具有最大字符串大小。

也可以在内存上执行标准 I/O,使用诸如 fmemopen() 之类的函数来获取引用内存而不是文件的 FILE *。现在,您的所有标准 I/O 函数都可以用于写入内存。甚至可以定义您自己的流类型,因此,例如,您可以编写一组过程,允许您使用 fprintf() 通过 SYSV IPC 消息“打印”到某些内容。

参考资料

GNU C 库参考手册 是一部非常庞大且全面的著作。虽然它并不完美并且仍在编写中,但它包含大量信息。我不知道它是否以纸质形式出版,但可以通过 ftp 从所有 gnu 镜像站点获得,并且可以轻松地从 emacs 或独立的 info 阅读器中打印或格式化以供在线阅读。

我将在这里占用一些空间来像往常一样推荐一些我发现最有帮助的书籍,我认为我的读者不应该没有这些书籍。

当您为现代 Unix 变体编程时,您不应该没有 W. Richard Stevens 的 Unix 环境高级编程,它包含您在大多数 Unix 变体下编写实际应用程序所需的大部分信息。涵盖了原理和细节。ISBN: 0-201-56317-7

为了学习如何编写可以在不仅仅是 Unix 平台上运行的 POSIX 兼容程序(我承认,这与本月专栏的主题恰恰相反),我推荐 Donald Lewine 的 POSIX 程序员指南。如果您遵循本书,则很难出错。ISBN: 0-937175-73-0

什么是 GNU 库公共许可证?

获取 FSF 代码

帮助!

我乐于接受关于人们希望看到哪些编程提示的建议。请发送电子邮件至 johnsonm@sunsite.unc.edu 或将纸质邮件发送至 Programming Tips, Linux Journal, P.O. Box 84867, Seattle, WA 98145-1867,我会考虑您的建议。如果您有任何您非常喜欢的书籍,并且您希望看到我在本专栏中推荐它们,请将它们推荐给我。如果您发现任何书籍作为 Unix 程序员是不可或缺的,我将不胜感激您提供详细的描述。

  1. 美国国家标准协会:美国国家标准 X3.159-1989-“ANSI C”。

加载 Disqus 评论