本节书摘来自异步社区《UNIX网络编程 卷2:进程间通信(第2版)》一书中的第1章,第1.6节,作者:【美】W. Richard Stevens著,更多章节内容可以访问云栖社区“异步社区”公众号查看
1.6 出错处理:包裹函数
在现实程序中,我们必须检查每个函数调用是否返回错误。由于碰到错误时终止程序执行是个惯例,因此我们可以通过定义包裹函数(wrapper function)来缩短程序的长度。包裹函数执行实际的函数调用,测试其返回值,并在碰到错误时终止进程。我们使用的命名约定是将函数名第一个字母改为大写字母,例如:
Sem_post(ptr);
图1-7定义了这个包裹函数。
每当你遇到一个以大写字母打头的函数名时,它就是我们所说的包裹函数。它调用一个名字相同但以相应小写字母开头的实际函数。当碰到错误时,包裹函数总是在输出一个出错消息后终止。
我们在讲解书中提供的源代码时,所指代的总是被调用的最低层函数(例如sem_post),而不是包裹函数(例如Sem_post)。类似地,书后的索引也总是指代被调用的最低层函数,而不是指代包裹函数。
刚刚展示的源代码格式全书都在使用。每一非空行都被编号。代码的正文说明部分的左边标有起始与结束的行号。有的段落开始处含有一个醒目的简短标题,概述本段代码的内容。
源代码片段起始与结束处的水平划线标出了该片段所在源代码文件名,本例中就是lib目录下的wrapunix.c文件。既然本书所有例子的源代码都可免费获得(见前言),你就可以凭这个文件名找到相应的文件。阅读本书的过程中,编译、运行并修改这些程序是学习进程间通信概念的好方法。
尽管包裹函数不见得如何节省代码量,当在第7章中讨论线程时,我们会发现线程函数出错时并不设置标准的Unix errno变量;相反,本该设置errno的值改由线程函数作为其返回值返回调用者。这意味着我们每次调用任意一个线程函数时,都得分配一个变量来保存函数返回值,然后在调用我们的err_sys函数(图C-4)前,把errno设置成所保存的值。为避免源代码中到处出现花括弧,我们可以使用C语言的逗号运算符,把给errno赋值与调用err_sys组合成单个语句,如下所示:
int n;
if ( (n = pthread_mutex_lock(&ndone_mutex)) != 0)
errno = n, err_sys("pthread_mutex_lock error");
另一种办法是定义一个新的出错处理函数,它需要的另一个参数是系统的错误号①。但是我们可以将这段代码简化得更容易些:
Pthread_mutex_lock(&ndone_mutex);
其前提是定义自己的包裹函数,如图1-8所示。
仔细推敲编码,我们可改用宏代替函数,从而稍稍提高运行效率,不过即使有过的话,包裹函数也很少是程序性能的瓶颈所在。
选择将函数名的第一个字母大写是一种较折中的方法。还有许多其他方法:例如用e作为函数名的前缀(如[Kernighan and Pike 1984]第184页所示),或者用_e作为函数名的后缀等。同样提供确实在调用某个其他函数的可视化指示,我们的方法看来是最少分散人们的注意力的。
这种技巧还有助于检查那些其错误返回值通常被忽略的函数,例如close和pthread_mutex_lock。
本书后面的例子中我们将普遍使用包裹函数,除非必须检查某个确定的错误并处理它(而不是终止进程)。我们并不给出所有包裹函数的源代码,但它们是免费可得的(见前言)。
Unix errno值
每当在一个Unix函数中发生错误时,全局变量errno将被设置成一个指示错误类型的正数,函数本身则通常返回-1。我们的err_sys函数检查errno的值并输出相应的出错消息,例如,errno的值等于EAGAIN时的出错消息为“Resource temporarily unavailable”(资源暂时不可用)。
errno的值只在某个函数发生错误时设置。如果该函数不返回错误,errno的值就无定义。所有正的错误值都是常值,具有以E打头的全部为大写字母的名字,通常定义在头文件中。没有值为0的错误。
在多线程环境中,每个线程必须有自己的errno变量。提供一个局限于线程的errno变量的隐式请求是自动处理的,不过通常需要告诉编译器所编译的程序是可重入的。给编译器指定类似-D_REENTRANT或-D_POSIX_C_SOURCE=199506L这样的命令行选项是较典型的方法。头文件往往把errno定义成一个宏,当常值_REENTRANT有定义时,该宏就扩展成一个函数,由它访问errno变量的某个局限于线程的副本。
全书使用类似“mq_send函数返回EMSGSIZE错误”的用语来简略地表示这样的意思:该函数返回一个错误(典型情况是返回值为-1),并且在errno中设置了指定的常值。