作者在本节中一直在强调要检查错误,书中也是这么做的,如下面一段程序:
cnt = read(fd, buf, numbytes); if(cnt == -1){ if(error == EINTR) fprintf(stderr, "read was interrupted by a signal\n"); else{ /*Some other error occured*/ } } 这段程序中,read是一个系统调用函数,用if条件语句检查函数的返回值是否为-1,如果为-1,说明调用出错,然后再用if条件语句检查erron确定错误原因。但是erron错误编号是有很多的,我们不可能将每个错误编号都判断一遍,于是作者为了简化程序中的错误处理,编写了错误诊断函数。 现在让我们当一回作者,考虑一下如何编写错误诊断函数? 首先,诊断错误要用到错误号,先弄一个数组存放这些erron对应的符号名,错误处理函数会使用该数组,去打印某个特定错误号相对应的符号名。作者这里用了一个数组ename[]来存放错误名。为什么要这么做?一是因为strerror()不会标识出与错误消息相对应的符号常量(如上例中的EINTR就是符号常量,它的错误号是4);另一方面,手册页在描述错误时,使用的是符号名称,打印出符号名称便于读者在手册页中查找错误原因。 接下来就要实现这些函数了,作者在这里定义了六个错误处理函数,errMsg()、errExit()、err_exit()、errExitEN()、fatal()、usageErr()、cmdLineErr() 这六个处理函数有各自的特点和使用范围,作者在书中已经有详细的说明,并在程序清单3-3中有它们的实现。我们不需要现在就记住这些函数是使用,只要在以后看到作者的程序清单用到了某个函数,考虑一下为什么在这里用,这样便能理解这些函数。 现在我们来研究一下程序清单3-3,看这几个函数是如何实现的。 从函数声明来看,这几个函数都是可变参数的,那么获取参数列表中的参数就是解决问题的关键。只要获取了可变参数,接下来按照p41~p43对每个函数的介绍,可以很容易理解。 <stdarg.h> 里面有几个清单中用到的函数: typedef char* va_list; void va_start ( va_list ap, prev_param ); type va_arg ( va_list ap, type ); void va_end ( va_list ap ); va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。 <Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap); <Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数; <Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置; <Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。 参考 http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 但是,作者在获取参数时没有调用va_arg,有的用到的是vfprintf()直接打印错误信息; 参考 http://baike.so.com/doc/175069-184966.html 而有的是调用自定义函数outputError(),在这个函数中打印错误信息。这个函数实现中调用了如下函数: vsnprintf参考 http://blog.csdn.net/sunshine_okey/article/details/7386081 snprintf参考 http://baike.so.com/doc/6733871-6948230.html getenv参考 http://baike.so.com/doc/6118960-6332105.html 还有些用到的函数这里没有提及,遇到不会的借鉴一下别人的经验,或者查看man手册,自己就能够解决。 四、解析数值型命令行参数的函数 我们知道,命令行可以传参,但数值型命令行参数传入的是字符串,要想把它转换成整型数,我们可以用atoi()、atol()、srttol(),而作者提供了两个函数getInt()、getLong(),这两个函数针对数值型参数提供了有效性检查,即如果arg未包含一个有效的整数字符串(即仅包含数字以及“+”和“-”),那么这两个函数会打印一条错误消息,并终止程序。 对于这两个函数的实现,程序清单3-6可以很容易看明白,唯一令博主不明白的是作者预定义了几个GN_*系列常量,与flag相或,是如何实现选择进制转换的?如果有研究过本段代码的朋友希望可以指点一二。 五、可移植性问题 这一小节中,作者向我们介绍了怎样编写自己的程序才能具有可移植性。看我这小节突然明白了之前一些不明白但却不影响学习的写法,比如说为啥非要把int重命名成pid_t。有些东西就是这样,初看不明白,可以先跳过去看后面的,等再次看的时候反而更加理解了。好了,如何使程序具有可移植性呢?博主把本小节归纳为以下几点: 1、特性测试宏:控制头文件显露对特定标准的定义; 2、使用系统数据类型声明变量; 3、正确初始化标准结构体; 4、使用预编译指令#ifdef(因为有的宏并不存在)。 六。课后练习 reboot()系统调用的参数magic2,没见过,不要紧,随便开个终端,man 2 reboot查看一下,第二个参数magic2的取值有4个: LINUX_REBOOT_MAGIC2 (that is, 672274793) LINUX_REBOOT_MAGIC2A (that is, 85072278) LINUX_REBOOT_MAGIC2B (that is, 369367448) LINUX_REBOOT_MAGIC2C (that is, 537993216) 这四个数字转换成十六进制数分别是: 672274793=0x28121969 85072278=0x05121996 369367448=0x16041998 537993216=0x20112000 看出什么来了吗?是不是很像日期呢?day,month,year,没错,就是生日。问了度娘,原来是Linux的作者Linus Torvalds自己和他三个女儿的生日。知道这个答案,我只想说一句:“原来可以这样!” 如果有读者看了已经发表过的这几篇读书笔记,会感觉到博主不是简单的知识点罗列,而是将书中的概念进行梳理不至于会被翻译弄的不知所云,博主在写每篇读书笔记时都会潜心研究书中内容,将已经具备的知识和书中内容进行融合,力求将此博文写成本书解析,最好可以冠以名为”Linux系统编程手册解析“。博主很乐意与别人分享知识和经验,这样做既可以帮助别人,也可以提高自己。