第十六章:标准函数库
16.5 信号
程序中所发生的事件绝大多数都是由程序本身所引起的,例如执行各种语句和请求输入。但是,有些程序必须遇到的事件却不是程序本身所引发的。
一个常见例子就是用户中断了程序。如果部分计算好的结果必须进行保存以避免数据的丢失,程序必须预备对这类事件作出反应,
虽然它没有办法预测什么时候会发生这种情况。
信号就是用于这种目的。信号(signal)表示一种事件,它可能异步地发生,也就是并不与程序实行过程中的任何事件同步。
如果程序并未安排怎样处理一个特定的信号,那么当该信号出现时程序就作出一个缺省的反应。标准并未定义这个缺省反应是什么,
但绝大多数编译器都选择终止程序。另外,程序可以调用signal函数,或者忽略这个信号,或者设置一个信号处理函数(siganl handler),
当信号发生时程序就调用这个函数。
<signal.h>
SIGABRT about函数引发的信号,请求异常终止
SIGFPE 算数错误,常见有算术上溢,下溢以及除零错误 同步!!
SIGILL 非法指令
SIGSEGV 检测到对内存的非法访问
这几个信号是同步的,因为它们都是在程序内部发生。而且每次运行都在同一个地方发生相同的错误。
SIGINT 收到一个交互性注意信号 异步!!
SIGTERM 收到一个终止程序的请求
它们在程序的外部产生,通常是由程序的用户所触发,表示用户试图向程序传达一些信息。
在实现了这两个信号的系统里,一种常见策略是为SIGINT 定义一个信号处理函数,目的是执行一些日常维护工作(housekeeping)并在程序退出前保存数据。
但是,SIGTERM则不配备信号处理函数,这样当程序终止时便不必执行这些日常维护工作。
int raise(int sig); 显式地引发一个信号
程序使用三种方式对它作出反应。缺省的反应有编译器定义,通常是终止程序;
信号被忽略;设置一个信号处理函数。
void (*signal(int sig, void (*handler)(int)))(int);
省略返回类型 signal(int sig, (*handler)(int));
从原型中去掉参数 void (*signal())(int); 返回一个函数指针
事实上,signal返回一个指向该信号以前的处理函数的指针。通过保存这个值,你可以为信号设置一个处理函数并在将来恢复为先前的处理函数。
如果调用signal失败,例如由于非法的代理函数所致,函数将返回SIG_ERR 。这个值是个宏,它在signal.h中定义。
signal.h 还定义了另外两个宏,SIG_DFL 和 SIG_IGN,作为signal函数的第2个参数。
SIG_DFL 恢复对该信号的缺省反应,SIG_IGN 使该信号被忽略。
信号处理函数
信号处理函数可能执行的工作类型是很有限的。如果信号是异步的,也就是说不是由于调用about 或 raise 函数引起的,
信号处理函数便不应该调用除signal之外的任何库函数,因为这种情况下其结果是未定义的。
而且,信号处理函数除了能向一个类型为volatile sig_atomic_t 的静态变量赋一个值外,可能无法访问其他任何静态数据。
为了保证真正的安全,信号处理函数所能做的就是对这些变量之一进行设置然后返回。程序的剩余部分必须定期检查变量的值,看看有没有信号发生。
这些严格的限制是由于信号处理的本质产生的。信号通常用于提示发生了错误,在这些情况下,CPU的行为是精确定义的,
但在程序中,错误所处的上下文环境可能很不相同,因此它们不一定能够良好定义。
警告
标准表示信号处理函数可以通过调用exit终止程序。用于除了SIGABRT之外所有信号的处理函数也可以调用about终止程序。
但是,由于这两个都是库函数,所以当他们被异步信号处理函数调用时可能无法正常运行。
volatile
信号可能在任何时候发生,所以由信号处理函数修改的变量的值可能在任何时候发生改变。因此,你不能指望这些变量在两条相邻的程序语句中具有相同的值。
volatile关键字告诉编译器这个事实,防止它以一种可能修改程序含义的方式“优化”程序。
例如
if(value){
printf("True\n");
}
else{
printf("False\n");
}
if(value){
printf("True\n");
}
else{
printf("False\n");
}
在普通情况下,你可能认为第二个测试和第一个测试具有相同的结果。如果信号处理函数修改了这个变量,第二个测试结果可能不同。
除非变量被声明为volatile,否则编译器可能会用下面的代码进行替换,从而对程序进行“优化”。
if(value){
printf("True\n");
printf("True\n");
}
else{
printf("False\n");
printf("False\n");
}
从一个信号处理函数返回导致程序的执行流从发生地点恢复执行。这个规则的例外情况是SIGFPE。由于计算无法完成,从这个信号返回的效果是未定义的。
警告
如果你希望捕捉将来同种类型的信号,从当前的处理函数返回之前注意要调用signal函数重新设置信号处理函数,
否则只有第一个信号才会被捕捉,接下来的信号将使用缺省反应进行处理。
16.6 打印可变参数列表 <stdarg.h>
int vprintf(char const *format, va_list arg);
int vfprintf(FILE *stream, char const *format, va_list arg);
int vsprintf(char *buffer, char const *format, va_list arg);
在调用这些函数之前,arg 参数必须使用va_start 进行初始化。
16.7 执行环境
终止执行 <stdlib.h>
void abort(void);
void atexit(void (func)(void));
void exit(int status);
abort 不正常终止一个正在执行的程序。引发SIGABRT信号。
atexit 可以把一些函数注册为退出函数(exit function)。当程序将要正常终止时,退出函数被调用。
exit 正常终止程序。如果main函数返回一个值结束,那么其效果相当于用这个值作为参数调用exit函数。
当exit 函数被调用时,所有被atexit函数注册为退出函数的函数将按照他们所注册的顺序被反序调用。然后所有流缓冲区被刷新,所有文件被关闭。
用tmpfile函数创建的文件被删除,然后,退出状态返回给宿主环境,程序停止工作。
警告
如果任何一个用atexit注册为退出函数的函数再次调用了exit,其效果是未定义的。这个错误可能导致一个无限循环,很可能只有当堆栈的内存耗尽后才停止。
断言 <assert.h>
断言就是声明某种东西应该为真。ANSI C 实现了assert宏,它在调试程序时很有用。
void assert(int experssion);
如果假(零),就向标准错误打印一条诊断信息并终止程序。这条信息的格式由编译器定义的,但它将包含这个表达式和源文件的名字以及断言所在的行号。
例如,如果一个函数必须用一个不能为NULL的指针参数进行调用,那么函数可以用断言验证这个值:
assert(value != NULL);
如果函数错误的接受了一个NULL参数,程序将打印一条类似下面形式的信息:
Assertion failed: value != NULL, file.c line 274
提示
用这种方法使用断言是调试变得更容易。
当程序被完整的测试完毕之后,你可以在编译时通过定义NDEBUG消除所有的断言。你可以使用-DNDEBUG编译器命令行选项或者在源文件中头文件assert.h被包含之前增加这个定义
#define NDEBUG
当NDEBUG被定义后,预处理器将丢弃所有的断言,这样就消除了这方面的开销,而不必从源文件中把所有的断言实际删除。
环境 <stdlib.h>
环境就是一个有编译器定义的名字/值对的列表,它由操作系统进行维护。getenv函数在这个列表中查找一个特定的名字,如果找到,返回一个指向其对应值的指针。
如果找不到,返回NULL
char *getenv(char const *name);
执行系统命令 <stdlib.h>
void system(char const *command);
排序和查找 <stdlib.h>
void qsort(void *base, size_t n_elements, size_t el_size, int (* compare)(void const *, void const *));
void *bsearch(void const *key, void const *base, size_tn_elements, size_t el_size, int (*compare)(void const *, void const *));
16.8 locale
"C" locale
16.5 信号
程序中所发生的事件绝大多数都是由程序本身所引起的,例如执行各种语句和请求输入。但是,有些程序必须遇到的事件却不是程序本身所引发的。
一个常见例子就是用户中断了程序。如果部分计算好的结果必须进行保存以避免数据的丢失,程序必须预备对这类事件作出反应,
虽然它没有办法预测什么时候会发生这种情况。
信号就是用于这种目的。信号(signal)表示一种事件,它可能异步地发生,也就是并不与程序实行过程中的任何事件同步。
如果程序并未安排怎样处理一个特定的信号,那么当该信号出现时程序就作出一个缺省的反应。标准并未定义这个缺省反应是什么,
但绝大多数编译器都选择终止程序。另外,程序可以调用signal函数,或者忽略这个信号,或者设置一个信号处理函数(siganl handler),
当信号发生时程序就调用这个函数。
<signal.h>
SIGABRT about函数引发的信号,请求异常终止
SIGFPE 算数错误,常见有算术上溢,下溢以及除零错误 同步!!
SIGILL 非法指令
SIGSEGV 检测到对内存的非法访问
这几个信号是同步的,因为它们都是在程序内部发生。而且每次运行都在同一个地方发生相同的错误。
SIGINT 收到一个交互性注意信号 异步!!
SIGTERM 收到一个终止程序的请求
它们在程序的外部产生,通常是由程序的用户所触发,表示用户试图向程序传达一些信息。
在实现了这两个信号的系统里,一种常见策略是为SIGINT 定义一个信号处理函数,目的是执行一些日常维护工作(housekeeping)并在程序退出前保存数据。
但是,SIGTERM则不配备信号处理函数,这样当程序终止时便不必执行这些日常维护工作。
int raise(int sig); 显式地引发一个信号
程序使用三种方式对它作出反应。缺省的反应有编译器定义,通常是终止程序;
信号被忽略;设置一个信号处理函数。
void (*signal(int sig, void (*handler)(int)))(int);
省略返回类型 signal(int sig, (*handler)(int));
从原型中去掉参数 void (*signal())(int); 返回一个函数指针
事实上,signal返回一个指向该信号以前的处理函数的指针。通过保存这个值,你可以为信号设置一个处理函数并在将来恢复为先前的处理函数。
如果调用signal失败,例如由于非法的代理函数所致,函数将返回SIG_ERR 。这个值是个宏,它在signal.h中定义。
signal.h 还定义了另外两个宏,SIG_DFL 和 SIG_IGN,作为signal函数的第2个参数。
SIG_DFL 恢复对该信号的缺省反应,SIG_IGN 使该信号被忽略。
信号处理函数
信号处理函数可能执行的工作类型是很有限的。如果信号是异步的,也就是说不是由于调用about 或 raise 函数引起的,
信号处理函数便不应该调用除signal之外的任何库函数,因为这种情况下其结果是未定义的。
而且,信号处理函数除了能向一个类型为volatile sig_atomic_t 的静态变量赋一个值外,可能无法访问其他任何静态数据。
为了保证真正的安全,信号处理函数所能做的就是对这些变量之一进行设置然后返回。程序的剩余部分必须定期检查变量的值,看看有没有信号发生。
这些严格的限制是由于信号处理的本质产生的。信号通常用于提示发生了错误,在这些情况下,CPU的行为是精确定义的,
但在程序中,错误所处的上下文环境可能很不相同,因此它们不一定能够良好定义。
警告
标准表示信号处理函数可以通过调用exit终止程序。用于除了SIGABRT之外所有信号的处理函数也可以调用about终止程序。
但是,由于这两个都是库函数,所以当他们被异步信号处理函数调用时可能无法正常运行。
volatile
信号可能在任何时候发生,所以由信号处理函数修改的变量的值可能在任何时候发生改变。因此,你不能指望这些变量在两条相邻的程序语句中具有相同的值。
volatile关键字告诉编译器这个事实,防止它以一种可能修改程序含义的方式“优化”程序。
例如
if(value){
printf("True\n");
}
else{
printf("False\n");
}
if(value){
printf("True\n");
}
else{
printf("False\n");
}
在普通情况下,你可能认为第二个测试和第一个测试具有相同的结果。如果信号处理函数修改了这个变量,第二个测试结果可能不同。
除非变量被声明为volatile,否则编译器可能会用下面的代码进行替换,从而对程序进行“优化”。
if(value){
printf("True\n");
printf("True\n");
}
else{
printf("False\n");
printf("False\n");
}
从一个信号处理函数返回导致程序的执行流从发生地点恢复执行。这个规则的例外情况是SIGFPE。由于计算无法完成,从这个信号返回的效果是未定义的。
警告
如果你希望捕捉将来同种类型的信号,从当前的处理函数返回之前注意要调用signal函数重新设置信号处理函数,
否则只有第一个信号才会被捕捉,接下来的信号将使用缺省反应进行处理。
16.6 打印可变参数列表 <stdarg.h>
int vprintf(char const *format, va_list arg);
int vfprintf(FILE *stream, char const *format, va_list arg);
int vsprintf(char *buffer, char const *format, va_list arg);
在调用这些函数之前,arg 参数必须使用va_start 进行初始化。
16.7 执行环境
终止执行 <stdlib.h>
void abort(void);
void atexit(void (func)(void));
void exit(int status);
abort 不正常终止一个正在执行的程序。引发SIGABRT信号。
atexit 可以把一些函数注册为退出函数(exit function)。当程序将要正常终止时,退出函数被调用。
exit 正常终止程序。如果main函数返回一个值结束,那么其效果相当于用这个值作为参数调用exit函数。
当exit 函数被调用时,所有被atexit函数注册为退出函数的函数将按照他们所注册的顺序被反序调用。然后所有流缓冲区被刷新,所有文件被关闭。
用tmpfile函数创建的文件被删除,然后,退出状态返回给宿主环境,程序停止工作。
警告
如果任何一个用atexit注册为退出函数的函数再次调用了exit,其效果是未定义的。这个错误可能导致一个无限循环,很可能只有当堆栈的内存耗尽后才停止。
断言 <assert.h>
断言就是声明某种东西应该为真。ANSI C 实现了assert宏,它在调试程序时很有用。
void assert(int experssion);
如果假(零),就向标准错误打印一条诊断信息并终止程序。这条信息的格式由编译器定义的,但它将包含这个表达式和源文件的名字以及断言所在的行号。
例如,如果一个函数必须用一个不能为NULL的指针参数进行调用,那么函数可以用断言验证这个值:
assert(value != NULL);
如果函数错误的接受了一个NULL参数,程序将打印一条类似下面形式的信息:
Assertion failed: value != NULL, file.c line 274
提示
用这种方法使用断言是调试变得更容易。
当程序被完整的测试完毕之后,你可以在编译时通过定义NDEBUG消除所有的断言。你可以使用-DNDEBUG编译器命令行选项或者在源文件中头文件assert.h被包含之前增加这个定义
#define NDEBUG
当NDEBUG被定义后,预处理器将丢弃所有的断言,这样就消除了这方面的开销,而不必从源文件中把所有的断言实际删除。
环境 <stdlib.h>
环境就是一个有编译器定义的名字/值对的列表,它由操作系统进行维护。getenv函数在这个列表中查找一个特定的名字,如果找到,返回一个指向其对应值的指针。
如果找不到,返回NULL
char *getenv(char const *name);
执行系统命令 <stdlib.h>
void system(char const *command);
排序和查找 <stdlib.h>
void qsort(void *base, size_t n_elements, size_t el_size, int (* compare)(void const *, void const *));
void *bsearch(void const *key, void const *base, size_tn_elements, size_t el_size, int (*compare)(void const *, void const *));
16.8 locale
"C" locale