下一篇:,上一篇:,上一级:程序行为   [目录][索引]


4.2 编写健壮的程序

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

读取文件的实用程序不应丢弃 NUL 字符或任何其他不可打印字符。程序应该能够正确处理多字节字符编码,例如 UTF-8。您可以使用 libiconv 来处理各种编码。

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

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

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

如果 malloc 在非交互式程序中失败,请将其设置为致命错误。在交互式程序(从用户读取命令的程序)中,最好中止命令并返回到命令读取器循环。这允许用户杀死其他进程以释放虚拟内存,然后再次尝试该命令。

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

当在程序执行期间要写入静态存储时,请使用显式 C 代码对其进行初始化。这样,重新启动程序(而不重新加载它)或部分程序将重新初始化这些变量。为不会更改的数据保留 C 初始化声明。

尽量避免使用低级接口来访问晦涩的 Unix 数据结构(例如文件目录、utmp 或内核内存的布局),因为这些不太可能兼容地工作。如果您需要查找目录中的所有文件,请使用 readdir 或其他一些高级接口。GNU 对这些提供兼容的支持。

首选的信号处理工具是 signal 的 BSD 变体和 POSIX sigaction 函数;替代的 USG signal 接口是一种较差的设计。

如今,使用 POSIX 信号函数可能是使程序可移植的最简单方法。如果您使用 signal,那么在运行 GNU libc 1 版本的 GNU/Linux 系统上,您应该包含 bsd/signal.h 而不是 signal.h,以便获得 BSD 行为。是否支持 signal 仅具有 USG 行为的系统,或者放弃对它们的支持,由您决定。

在检测到“不可能”条件的错误检查中,只需中止即可。通常没有必要打印任何消息。这些检查表明存在错误。任何想要修复错误的人都必须阅读源代码并运行调试器。因此,请在源代码中使用注释解释问题。相关数据将在变量中,这些变量很容易使用调试器检查,因此没有必要将它们移动到其他地方。

不要使用错误计数作为程序的退出状态。这不起作用,因为退出状态值限制为 8 位(0 到 255)。程序的单次运行可能出现 256 个错误;如果您尝试返回 256 作为退出状态,父进程将看到 0 作为状态,并且似乎程序成功了。

如果您创建临时文件,请检查 TMPDIR 环境变量;如果定义了该变量,请使用指定的目录而不是 /tmp

此外,请注意在可写目录中创建临时文件时可能存在安全问题。在 C 中,您可以通过这种方式创建临时文件来避免此问题

fd = open (filename, O_WRONLY | O_CREAT | O_EXCL, 0600);

或者使用来自 Gnulib 的 mkstemps 函数(请参阅 mkstemps,在Gnulib中)。

在 bash 中,使用 set -C(长名称 noclobber)来避免此问题。此外,mktemp 实用程序是从 shell 脚本创建临时文件的更通用的解决方案(请参阅 mktemp invocation,在 GNU Coreutils 中)。


下一篇:,上一篇:,上一级:程序行为   [目录][索引]