C 编程/错误处理
C 语言不直接支持错误处理(也称为异常处理)。按照惯例,程序员应该首先防止错误发生,并测试函数的返回值。例如,-1 和 NULL 分别用于 socket()(Unix 套接字编程)或 malloc() 等多个函数,以指示程序员应该注意的问题。在最坏的情况下,如果出现不可避免的错误并且无法从中恢复,C 程序员通常会尝试记录错误并“优雅地”终止程序。
有一个名为“errno”的外部变量,在包含 <errno.h> 后,程序可以访问该变量 - 该文件来自某些操作系统(例如 Linux - 在这种情况下,定义位于 include/asm-generic/errno.h 中)中可能发生的错误的定义,当程序请求资源时。此变量索引由函数 'strerror( errno )' 访问的错误描述。
以下代码测试库函数 malloc 的返回值,以查看动态内存分配是否已正确完成
#include <stdio.h> /* perror */
#include <errno.h> /* errno */
#include <stdlib.h> /* malloc, free, exit */
int main(void)
{
/* Pointer to char, requesting dynamic allocation of 2,000,000,000
* storage elements (declared as an integer constant of type
* unsigned long int). (If your system has less than 2 GB of memory
* available, then this call to malloc will fail.)
*/
char *ptr = malloc(2000000000UL);
if (ptr == NULL) {
perror("malloc failed");
/* here you might want to exit the program or compensate
for that you don't have 2GB available
*/
} else {
/* The rest of the code hereafter can assume that 2,000,000,000
* chars were successfully allocated...
*/
free(ptr);
}
exit(EXIT_SUCCESS); /* exiting program */
}
上面的代码片段展示了使用库函数 malloc 的返回值来检查错误。许多库函数都有返回值来标记错误,因此应该由精明的程序员进行检查。在上面的代码片段中,从 malloc 返回的 NULL 指针表示分配错误,因此程序退出。在更复杂的实现中,程序可能会尝试处理错误并尝试从失败的内存分配中恢复。
C 程序员常犯的一个错误是在执行除法命令之前没有检查除数是否为零。以下代码将产生运行时错误,并且在大多数情况下会退出。
int dividend = 50;
int divisor = 0;
int quotient;
quotient = (dividend/divisor); /* This will produce a runtime error! */
在普通算术中,除以零 是未定义的。因此,您必须检查或确保除数永远不为零。或者,对于 *nix 进程,您可以通过阻止 SIGFPE 信号来阻止操作系统终止您的进程。
以下代码通过在除法之前检查除数是否为零来修复此问题。
#include <stdio.h> /* for fprintf and stderr */
#include <stdlib.h> /* for exit */
int main( void )
{
int dividend = 50;
int divisor = 0;
int quotient;
if (divisor == 0) {
/* Example handling of this error. Writing a message to stderr, and
* exiting with failure.
*/
fprintf(stderr, "Division by zero! Aborting...\n");
exit(EXIT_FAILURE); /* indicate failure.*/
}
quotient = dividend / divisor;
exit(EXIT_SUCCESS); /* indicate success.*/
}
在某些情况下,环境可能会通过发出信号来响应 C 语言中的编程错误。信号是由主机环境或操作系统发出的事件,表示已发生特定错误或严重事件(例如除以零、中断等)。但是,这些信号并非旨在用作错误捕获的手段;它们通常表示会干扰正常程序流程的严重事件。
为了处理信号,程序需要使用signal.h头文件。需要定义一个信号处理程序,然后调用 signal() 函数以允许处理给定的信号。一些在代码中引发异常的信号(例如除以零)不太可能让程序恢复。这些信号处理程序将需要改为确保在程序终止之前正确清理某些资源。
C 标准库只定义了六个信号;Unix 系统定义了另外 15 个。每个信号都有一个与之关联的数字,称为信号号。
#define SIGHUP 1 /* Hangup the process */
#define SIGINT 2 /* Interrupt the process. C standard */
#define SIGQUIT 3 /* Quit the process */
#define SIGILL 4 /* Illegal instruction. C standard.*/
#define SIGTRAP 5 /* Trace trap, for debugging. C standard.*/
#define SIGABRT 6 /* Abort. C standard. */
#define SIGFPE 8 /* Floating Point Error. C standard. */
#define SIGSEGV 11 /* Memory error. C standard. */
#define SIGTERM 15 /* Termination request. C standard. */
信号使用signal()函数处理,来自signal.h库。其语法为
void signal(signal_to_catch, signal_handler)
信号可以用raise()或kill(). raise()发出信号给当前进程;kill()发出信号给特定进程。
请注意signal现在已被弃用,建议使用sigaction(),因为 Unix 系统之间缺乏可移植性以及可能出现意外行为。但是,由于sigaction()的使用更加复杂,我们将坚持使用signal()来在此处说明概念。
为了理解信号的工作原理,这里有一个简单的示例
#include <stdio.h>
#include <unistd.h> // Unix Standard library, used to import sleep()
#include <stdlib.h>
#include <signal.h>
void handler(int signum) {
printf("Signal received %d, coming out...\n", signum);
exit(1);
}
int main () {
signal(SIGINT, handler); // attaching the handler() function to SIGINT signals; i.e, ctrl+c, keyboard interrupt.
while(1) {
printf("Sleeping...\n");
sleep(1000); // sleep pauses the process for a given number of seconds, or until a signal is received.
}
return(0);
}
尝试在您的机器上编译并测试此代码;在您看到“Sleeping...”后,通过按下ctrl + c.
发送中断信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
static void catch_function(int signal) {
puts("Interactive attention signal caught.");
}
int main(void) {
if (signal(SIGINT, catch_function) == SIG_ERR) {
fputs("An error occurred while setting a signal handler.\n", stderr);
return EXIT_FAILURE;
}
puts("Raising the interactive attention signal.");
if (raise(SIGINT) != 0) {
fputs("Error raising the signal.\n", stderr);
return EXIT_FAILURE;
}
puts("Exiting.");
return 0;
}
这里有一个更复杂的示例。这将创建一个信号处理程序并发出信号
setjmp这里有一个更复杂的示例。这将创建一个信号处理程序并发出信号可以使用 setjmp 函数来模拟其他编程语言的异常处理功能。对 setjmp 的第一次调用存储对当前执行点的引用点,并且只要包含 setjmp() 的函数不返回或退出,该引用点就有效。对 longjmp 的调用会导致执行返回到关联的 setjmp 调用的点。0将一个 `jmp_buf`(将存储执行上下文的类型)作为参数,并在第一次运行时返回(即,当它设置返回值时)。当它第二次运行时 - 当longjmp(即,当它设置返回值时)。当它第二次运行时 - 当.
(即,当它设置返回值时)。当它第二次运行时 - 当被调用时 - 然后它返回传递给这里有一个更复杂的示例。这将创建一个信号处理程序并发出信号的值。将一个 `jmp_buf`(已经传递给这里有一个更复杂的示例。这将创建一个信号处理程序并发出信号)和一个传递给
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
int main(void) {
int val;
jmp_buf environment;
val = setjmp(environment); // val is set to 0 the first time this is called
if (val !=0)
{
printf("You returned from a longjmp call, return value is %d", val); // now, value is 1, passed from longjmp()
exit(0);
}
puts("Calling longjmp now");
longjmp(environment, 1);
return(0);
}
的值作为参数。
尝试在您自己的机器上使用编译器运行此代码。
当 setjmp 从 longjmp 调用返回时,非易失变量的值可能会被破坏。