地产金融网站开发,研究网站开发意义,做营销网站,wordpress用手机写博客目录
库函数 一、返回整数的getchar 函数
getchar 函数
二、更新顺序文件 三、缓冲输出与内存分配
程序输出
四、使用errno 检测错误
五、库函数signal 库函数
C语言中没有定义输入/输出语句#xff0c;任何一个有用的C程序#xff08;起码必须接受零个或多个输入任何一个有用的C程序起码必须接受零个或多个输入生成一个或多个输出)都必须调用库函数来完成最基本的输入/输出操作。ANSIC 标准毫无疑问地意识到了这一点因而定义了一个包含大量标准库函数的集合。从理论上说任何一个C语言实现都应该提供这些标准库函数。
有关库函数的使用我们能给出的最好建议是尽量使用系统头文件。特别是库文件的编写者已经提供了精确描述库函数的头文件在ANSI C 中这一点尤其重要因为头文件中包括了库函数的参数类型以及返回类型的声明。 一、返回整数的getchar 函数
我们首先考虑下面的例子# include stdio. h
main() { char c ; whiie( ( c getchar( )) ! EOF) putchar (c);
} getchar 函数
getchar 函数在一般情况下返回的是标准输入文件中的下一个字符当没有输入时返回EOF(一个在头文件stdio .h中被定义的值不同于任何一个字符)。这个程序乍一看似乎是把标准输入复制到标准输出实则不然。 原因在于程序中的变量c被声明为char类型而不是int类型。这意味着c无法容下所有可能的字符特别是可能无法容下EOF。
因此最终结果存在三种可能。
1.某些合法的输入字符在被“截断”后使得c的取值与EOF相同。
2.c 根本不可能取到EOF这个值。对于前一种情况程序将在文件复制的中途终止对于后一种情况程序将陷入一个死循环。
3.程序表面上似乎能够正常工作但完全是因为巧合。尽管函数getchar 的返回结果在赋给char类型的变量c时会发生“截断”操作尽管while 语句中比较运算的操作数不是函数getchar 的返回值而是被“截断”的值c,然而许多编译器对上述表达式的实现并不正确。这些编译器确实对函数getchar 的返回值作了“截断”处理并把低端字节部分赋给了变量c。但是它们在比较表达式中并不是比较c与EOF,而是比较getchar 函数的返回值与EOF!编译器如果采取的是这种做法上面的例子程序看上去就能够“正常”运行了.
二、更新顺序文件
许多系统中的标准输入/输出库都允许程序打开一个文件同时进行写入和读出的操作FILE * fp fp fopen ( file, r); 上面的例子代码打开了文件名由变量file指定的文件对于存取权限的设定表明程序希望对这个文件进行输入和输出操作。 编程者也许认为程序一旦执行上述操作完毕就可以自由地交错进行读出和写入的操作。遗憾的是事实总难遂人所愿为了保持与过去不能同时进行读写操作的程序的向下兼容性一个输入操作不能随后直接紧跟一个输出操作反之亦然。如果要同时进行输入和输出操作必须在其中插入fseek 函数的调用。 下面的程序片段似乎更新了一个顺序文件中选定的记录FILE * fp struct record rec .. while (fread(( char * ) rec, sizeof( rec) , 1, fp) 1) { /*对rec执行某些操作*/ if(/*rec必须被重新写入*/){ fseek(fp,-(long)sizeof(rec),1); fwrite( (char * ) rec, sizeof( rec) , 1, fp); } } 这段代码乍看上去毫无问题rec在传入fread 和fwrite 函数时被转换为字符指针类型sizeof (rec)被转换为长整型(fseek 函数要求第二个参数是long类型因为int类型的整数可能无法包含一个文件的大小sizeof 返回一个unsigned 值因此首先必须将其转换为有符号类型才有可能将其反号)。但是这段代码仍然可能运行失败而且出错的方式非常难于察觉。 问题出在如果一个记录需要被重新写入文件也就是说fwrite 函数得到执行对这个文件执行的下一个操作将是循环开始的fread 函数。因为在fwrite 函数调用与fread 函数调用之间缺少了一个fseek 函数调用所以无法进行上述操作。解决的办法是把这段代码改写为FILE * fp struct record rec .. while (fread(( char * ) rec, sizeof( rec) , 1, fp) 1) { /*对rec执行某些操作*/ if(/*rec必须被重新写入*/){ fseek(fp,-(long)sizeof(rec),1); fwrite( (char * ) rec, sizeof( rec) , 1, fp); fseek( fp, OL, 1); } } 第二个fseek 函数虽然看上去什么也没做但它改变了文件的状态使得文件现在可以正常地进行读取了。 三、缓冲输出与内存分配
程序输出
程序输出有两种方式一种是即时处理方式另一种是先暂存起来然后再大块写入的方式前者往往造成较高的系统负担。因此C语言实现通常都允许程序员进行实际的写操作之前控制产生的输出数据量。 这种控制能力一般是通过库函数setbuf 实现的。如果buf是一个大小适当的字符数组那么setbuf( stdout, buf
语句将通知输入输出库所有写入到stdout 的输出都应该使用buf作为输出缓冲区直到buf缓冲区被填满或者程序员直接调用fflush (译注对于由写操作打开的文件调用fflush 将导致输出缓冲区的内容被实际地写入该文件)buf缓冲区中的内容才实际写入到stdout 中。缓冲区的大小由系统头文件stdio .h中的BUFSIZ 定义。
下面的程序的作用是把标准输入的内容复制到标准输出中演示了setbuf 库函数最显而易见的用法
#include stdio.h
main( )
{ int c ; char buf [BUFSIZ ]; setbuf( stdout, buf); while(( c getchar( ) ) ! EOF ) putchar(c); } 遗憾的是这个程序是错误的仅仅是因为一个细微的原因。程序中对库函数setbuf 的调用通知了输入输出库所有字符的标准输出应该首先缓存在buf中。要找到问题出自何处我们不妨思考一下buf缓冲区最后一次被清空是在什么时候答案是在main函数结束之后作为程序交回控制给操作系统之前C运行时库所必须进行的清理工作的一部分。但是在此之前buf字符数组已经被释放
要避免这种类型的错误有两种办法。
第一种办法是让缓冲数组成为静态数组既可以直接显式声明buf为静态static char buf [ BUFSLZ];
也可以把buf声明完全移到main函数之外。
第二种办法是动态分配缓冲区在程序中并不主动释放分配的缓冲区译注由于缓冲区是动态分配的所以main函数结束时并不会释放该缓冲区这样C运行时库进行清理工作时就不会发生缓冲区已释放的情况)char * malloc() setbuf( stdout, malloc(BUFSIZ)) 如果读者关心一些编程“小技巧”也许会注意到这里其实并不需要检查malloc 函数调用是否成功。如果malloc 函数调用失败将返回一个null指针。setbuf 函数的第二个参数取值可以为nul,此时标准输出不需要进行缓冲。这种情况下程序仍然能够工作只不过速度较慢而已。 四、使用errno 检测错误
很多库函数特别是那些与操作系统有关的当执行失败时会通过一个名称为errno 的外部变量通知程序该函数调用失败。下面的代码利用这一特性进行错误处理似乎再清楚明白不过然而却是错误的/*调用库函数*/ if (errno) /*处理错误* 出错原因在于在库函数调用没有失败的情况下并没有强制要求库函数一定要设置errno 为0这样errno 的值就可能是前一个执行失败的库函数设置的值。下面的代码作了更正似乎能够工作很可惜还是错误的errno 0 /*调用库函数*/ if (errno) /*处理错误*/ 库函数在调用成功时既没有强制要求对errno 清零但同时也没有禁止设置errno 。既然库函数已经调用成功为什么还有可能设置errno 呢要理解这一点我们不妨假想一下库函数fopen 在调用时可能会发生什么情况。
当fopen 函数被要求新建一个文件以供程序输出时如果已经存在一个同名文件fopen 函数将先删除它然后新建一个文件。
这样fopen 函数可能需要调用其他的库函数以检测同名文件是否已经存在。译注假设用于检测文件的库函数在文件不存在时会设置errno 。那么fopen 函数每次新建一个事先并不存在的文件时即使没有任何程序错误发生errno 也仍然可能被设置。)
因此在调用库函数时我们应该首先检测作为错误指示的返回值确定程序执行已经失败。然后再检查errno ,来搞清楚出错原因
/*调用库函数*/ if(返回的错误值) 检查errno ; 五、库函数signal
实际上所有的C语言实现中都包括有signal 库函数作为捕获异步事件的一种方式。要使用该库函数需要在源文件中加上
# include signal.h
以引入相关的声明。要处理一个特定的signal (信号)可以这样调用signal 函数
signal ( signal type, handler function );
这里的signal type 代表系统头文件signal.h 中定义的某些常量这些常量用来标识signal 函数将要捕获的信号类型。
这里的handler function 是当指定的事件发生时将要加以调用的事件处理函数。
在许多C语言实现中信号是真正意义上的“异步”。从理论上说一个信号可能在C程序执行期间的任何时刻上发生。需要特别强调的是信号甚至可能出现在某些复杂库函数如malloc 的执行过程中。因此从安全的角度考虑信号的处理函数不应该调用上述类型的库函数。 例如假设malloc 函数的执行过程被一个信号中断。此时malloc 函数用来跟踪可用内存的数据结构很可能只有部分被更新。如果signal 处理函数再调用malloc 函数结果可能是malloc 函数用到的数据结构完全崩溃
基于同样的原因从signal 处理函数中使用longjmp 退出通常情况下也是不安全的因为信号可能发生在malloc 或者其他库函数开始更新某个数据结构却又没有最后完成的过程中。
因此signal 处理函数能够做的安全的事情似乎就只有设置一个标志然后返回期待以后主程序能够检查到这个标志发现一个信号已经发生。
然而就算这样做也并不总是安全的。当一个算术运算错误例如溢出或者零作除数)引发一个信号时某些机器在signal 处理函数返回后还将重新执行失败的操作。而当这个算术运算重新执行时我们并没有一个可移植的办法来改变操作数。这种情况下最可能的结果就是马上又引发一个同样的信号。因此对于算术运算错误signal 处理函数的惟一安全、可移植的操作就是打印一条出错消息然后使用longjmp 或exit立即退出程序。 由此我们得到的结论是信号非常复杂棘手而且具有一些从本质上而言不可移植的特性。解决这个问题我们最好采取“守势”让signal 处理函数尽可能地简单并将它们组织在一起。这样当需要适应一个新系统时我们可以很容易地进行修改。