断言在测试和调试期间非常有用。断言宏的作用在于检查它的布尔参数是否为真(也就是非零);若为真,则不做任何工作;若为假,则程序以说明断言失败的消息终止。导致断言失败的布尔条件也会被显示。可以通过定义 NDEBUG 宏关闭断言:既可以通过编辑源代码添加一行“#define NDEBUG”(无需给出 #define 的值),也可以通过在 cc 或 gcc 编译器命令行上包含命令行开关“-DNDEBUG”在编译过程中实时完成,如下所示:
$ cc -DNDEBUG -o selpg selpg.c
断言可用于代码中的任何地方,不过函数的入口和出口是使用它们的好地方,您可以在那里检查前置条件和后置条件,因为您知道如果代码正确这些条件应该存在。那就是说,如果函数有确定的有效值集合或范围可作为参数,可以将检验参数是否有效的断言放在该函数代码的第一行。例如,如果计算平方根的函数只能处理非负数,您可以设置一个输入参数 >= 0 的断言。这意味着调用函数的职责是确保只传递非负参数。如果给出一个负数,就会触发断言并以条件“输入参数 >= 0”失败的消息终止该程序。类似地,在函数的末尾,如果您知道应该有某些后置条件,那就编写对应于各项条件的断言。如果由于该函数中代码的错误,您期望退出时为真的条件并不为真,那么这些断言能够确保您知道这一点。
使用断言的好方法是构建两个版本的最终应用程序 — 一个版本禁用断言(称其为发行版),而另一个版本启用断言(称其为调试版)。最初,将发行版给用户。如果用户在其它位置,则将调试版也给用户,但要放在单独的目录中。当在应用程序中发现问题时,告诉他们临时用调试版代替发行版并再次模拟该问题。这一次,断言所引起的消息将可能有助于查明错误来源。
可通过 make 实用程序将调试版本和发行版本的制作自动化。有关这一点的简单示例,请参阅随附的 makefile(在本文后面的参考资料中)。make 是将从一个或多个文件构建应用程序的过程自动化的实用程序。它可以处理相关性,例如有这样的规则:如果源文件被修改,那么必须通过重新编译该源文件来重新生成其对应的目标文件。它还可以做许多其它的事情。
以注释“set the output destination”开始的行
如果没有给出“-dDestination”选项,则写至标准输出。否则,我们尝试用命令字符串“lp -dDestination”打开到 lp 命令的管道。这是用系统调用 popen() 完成的,它打开到另一个进程的管道以用于读或写。在此情况下,我们以写方式打开到 lp 的管道。这意味着所有来自 selpg 进程的标准输出将进入 lp 进程的标准输入。如果 popen() 失败,则进行出错退出。
以注释“begin one of two main loops”开始的行
我们根据输入的类型执行两个循环中的一个。
若页类型是每页行数固定的,则使用 fgets() 库函数逐行读取输入。(对于错误或 EOF [文件结束],它都返回 NULL,这是在 stdio.h 中定义的;循环结束后,我们会检查究竟是哪种情况。)如果不是 NULL,则增加行计数器。然后检查行的总数是否超过页长。若是,则将页计数器加 1 并将行计数器复位为 0。然后检查页计数器是否在请求的页范围之内,若是,则写当前行;否则不写。重复该循环直至 fgets() 返回 NULL。注:fgets() 最多从 fin 读取 BUFSIZ - 1 个字符并将它们存储在字符串行。它附加一个 NULL 字符以使字符串行包含一个正确的以空值终止的 C 字符串。
用于换页定界的页的逻辑大体上相同,只是稍稍简单一些,因为不需要行计数器:使用 getc() 库函数逐个读取字符。该函数对于错误或文件结束都返回 EOF。检查每个读取的字符是否为换页符;若是,则增加页计数器。与每页行数固定的情况一样,仅当页在指定范围之内才写输出。重复该循环直至 getc() 返回 EOF。