转自:网络
C语言是一种低级的、静态的、结构化的编程语言,它没有提供像C++或Java等高级语言中的异常处理机制,例如try-catch-finally等。
因此,C语言中的错误处理和异常处理需要采用一些其他的方法和策略,以便在程序运行过程中发现、报告和处理错误或异常情况,从而保证程序的正确性和稳定性。
本文将介绍C语言中的错误处理和异常处理的一些常用的方法和策略,以及如何使用setjmp和longjmp这两个标准库函数来实现非局部跳转,从而在某些情况下模拟异常处理的效果。

错误处理和异常处理的概念

在讨论C语言中的错误处理和异常处理之前,我们先来区分一下错误和异常这两个概念。一般来说,错误是指程序中存在的逻辑或语法上的缺陷,导致程序无法按照预期的方式运行或产生正确的结果。
例如,数组越界、空指针解引用、除零操作等都是典型的错误。错误通常是可以通过修改代码来避免或修复的。
而异常是指程序在运行过程中遇到了一些意料之外或无法控制的情况,导致程序无法继续正常运行或完成预期的任务。
例如,文件打开失败、内存分配失败、信号中断等都是典型的异常。异常通常是由于外部环境或系统资源的变化或限制所引起的,不一定是程序本身的缺陷所导致的。
因此,错误处理和异常处理有不同的目标和方法。错误处理主要是在编码阶段通过检查代码逻辑、使用调试工具、进行单元测试等方式来发现并消除错误。
而异常处理主要是在运行阶段通过检查函数返回值、使用信号处理函数、设置错误处理函数等方式来捕获并处理异常。

错误处理和异常处理的方法和策略

C语言中没有提供统一的错误处理和异常处理机制,但是提供了一些基本的工具和约定,可以根据不同的情况选择合适的方法和策略来进行错误处理和异常处理。以下是一些常用的方法和策略:
  • 检查函数返回值:这是最常见也最基本的错误处理和异常处理方法,就是在调用一个函数后,检查其返回值是否符合预期或是否表示出错或失败。如果出错或失败,则根据返回值或者全局变量errno(定义在errno.h头文件中)来判断出错或失败的原因,并采取相应的措施,例如打印出错信息、释放资源、返回错误码等。例如:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

intmain()
{

// 打开一个文件
FILE *fp = fopen(
"test.txt"
,
"r"
);

// 检查文件是否打开成功
if
(fp ==
NULL
) {

// 打印出错信息
perror(
"fopen"
);

// 返回非零值表示出错
return1
;

}

// 读取文件内容
char
buf[
100
];

// 检查文件是否读取成功
if
(fgets(buf,
100
, fp) ==
NULL
) {

// 打印出错信息
perror(
"fgets"
);

// 关闭文件
fclose(fp);

// 返回非零值表示出错
return2
;

}

// 打印文件内容
printf
(
"The content of the file is: %s\n"
, buf);

// 关闭文件
fclose(fp);

// 返回零值表示成功
return0
;

}

  • 使用assert宏:这是一种用于调试阶段的错误处理方法,就是在代码中插入一些断言,用于检查程序的某些假设或前提是否成立。如果断言失败,则表示程序中存在逻辑错误,程序会终止并打印出错信息。assert宏定义在assert.h头文件中,其语法为:
assert
(expression);

其中expression是一个表达式,如果为真,则继续执行后面的代码;如果为假,则终止程序并打印出错信息。例如:
#include<stdio.h>
#include<assert.h>

intmain()
{

// 定义一个变量
int
x =
10
;

// 断言x大于0
assert(x >
0
);

// 打印x的值
printf
(
"x is %d\n"
, x);

// 修改x的值
x =
-10
;

// 断言x大于0
assert(x >
0
);

// 打印x的值
printf
(
"x is %d\n"
, x);

return0
;

}

输出:
xis10
Assertion failed:(x>0),functionmain,filetest.c,line15.
Abort trap:6
可以看到,当第二个断言失败时,程序就终止了,并打印了出错信息,包括断言的表达式、函数名、文件名和行号。这样可以方便地定位错误的位置和原因。需要注意的是,assert宏只在调试阶段有效,如果在编译时定义了宏NDEBUG,则assert宏会被忽略,不会对程序产生任何影响。例如:
gcc -DNDEBUG test.c -o
test
这样编译后,即使断言失败,程序也不会终止,而是继续执行后面的代码。
  • 使用信号处理函数:这是一种用于处理运行时异常的方法,就是在程序中注册一些信号处理函数,用于响应系统或用户发送的一些信号。信号是一种软件中断,用于通知进程发生了某些异常或事件。例如,当程序试图访问非法内存地址时,系统会发送SIGSEGV信号;当用户按下Ctrl-C键时,系统会发送SIGINT信号;当程序执行除零操作时,系统会发送SIGFPE信号等。C语言提供了signal函数来设置信号处理函数,其语法为:
void
(*signal(
int
signum,
void
(*handler)(
int
)))(
int
);

其中signum是要处理的信号的编号,handler是要设置的信号处理函数的指针。如果handler为SIG_IGN,则表示忽略该信号;如果handler为SIG_DFL,则表示恢复该信号的默认处理方式。signal函数返回一个指针,指向之前设置的信号处理函数。例如:
#include<stdio.h>
#include<signal.h>

// 定义一个信号处理函数
voidhandler(int signum)
{

// 打印收到的信号编号
printf
(
"Received signal %d\n"
, signum);

}


intmain()
{

// 设置SIGINT信号的处理函数为handler
signal(SIGINT, handler);

// 循环等待用户输入
while
(
1
) {

char
c = getchar();

// 如果输入q,则退出循环
if
(c ==
'q'
) {

break
;

}

}

return0
;

}

运行结果:
^CReceivedsignal2
^CReceivedsignal2
q
可以看到,当用户按下Ctrl-C键时,程序不会终止,而是调用了自定义的信号处理函数,并打印了收到的信号编号(2表示SIGINT)。当用户输入q时,程序才退出循环。
  • 使用setjmp和longjmp函数:这是一种用于实现非局部跳转的方法,就是在程序中设置一个跳转点,并在某些情况下跳转到该跳转点,从而绕过中间的一些代码或函数。这样可以在某些情况下模拟异常处理的效果,例如在发生错误或异常时,直接跳转到错误处理或资源释放的代码,而不需要逐层返回。setjmp和longjmp函数定义在setjmp.h头文件中,其语法为:
intsetjmp(jmp_buf env)
;

voidlongjmp(jmp_buf env, int val)
;

其中env是一个用于存储跳转点信息的数据类型,它实际上是一个数组,包含了程序计数器、栈指针、寄存器等信息。val是一个用于传递跳转原因的整数值,它不能为0。setjmp函数用于设置跳转点,并返回0;longjmp函数用于跳转到跳转点,并使setjmp函数返回val。例如:
#include<stdio.h>
#include<setjmp.h>

// 定义一个全局的env变量
jmp_buf env;


// 定义一个可能发生错误的函数
voidfoo(int x)
{

// 如果x为0,则发生除零错误,跳转到env,并传递1
if
(x ==
0
) {

longjmp(env,
1
);

}

// 否则,正常执行,并打印结果
printf
(
"100 / %d = %d\n"
, x,
100
/ x);

}


intmain()
{

// 设置跳转点,并接收返回值
int
ret = setjmp(env);

// 如果返回值为0,则表示正常执行
if
(ret ==
0
) {

// 调用foo函数,传入一个非零值
foo(
10
);

// 调用foo函数,传入一个零值
foo(
0
);

}
else
{

// 如果返回值不为0,则表示发生错误或异常,根据返回值打印出错信息
switch
(ret) {

case1
:

printf
(
"Error: division by zero\n"
);

break
;

default
:

printf
(
"Unknown error\n"
);

break
;

}

}

return0
;

}

输出:
100/10=10
Error:divisionbyzero
可以看到,当调用foo函数时,如果传入的参数为0,则会触发longjmp函数,从而跳转到setjmp函数所在的位置,并使setjmp函数返回1。这样就可以根据返回值来判断发生了什么错误或异常,并进行相应的处理。需要注意的是,使用setjmp和longjmp函数时要遵循一些规则和限制,例如:
  • 不要在setjmp和longjmp之间修改env变量的内容。
  • 不要在setjmp和longjmp之间修改任何具有全局或静态存储期的变量。
  • 不要在setjmp和longjmp之间调用任何可能改变程序状态或资源的函数。
  • 不要在多线程环境中使用setjmp和longjmp函数。

总结

C语言中的错误处理和异常处理需要采用一些其他的方法和策略,以便在程序运行过程中发现、报告和处理错误或异常情况,从而保证程序的正确性和稳定性。
本文介绍了C语言中的错误处理和异常处理的一些常用的方法和策略,以及如何使用setjmp和longjmp函数来实现非局部跳转,从而在某些情况下模拟异常处理的效果。希望这些内容能够对你有所帮助,在C语言中更好地进行错误处理和异常处理。
继续阅读
阅读原文