长文解析:嵌入式C编程之错误处理(超全代码)
扫描二维码
随时随地手机看文章
前言
一、错误概念
1.1 错误分类
1.2 处理步骤
-
程序执行时发生软件错误。该错误可能产生于被底层驱动或内核映射为软件错误的硬件响应事件(如除零)。 -
以一个错误指示符(如整数或结构体)记录错误的原因及相关信息。 -
程序检测该错误(读取错误指示符,或由其主动上报); -
程序决定如何处理错误(忽略、部分处理或完全处理); -
恢复或终止程序的执行。
int func()
{
int bIsErrOccur = 0;
//do something that might invoke errors
if(bIsErrOccur) //Stage 1: error occurred
return -1; //Stage 2: generate error indicator
//...
return 0;
}
int main(void)
{
if(func() != 0) //Stage 3: detect error
{
//Stage 4: handle error
}
//Stage 5: recover or abort
return 0;
}
二 、错误传递
2.1 返回值和回传参数
if((p = malloc(100)) == NULL)
//...
if((c = getchar()) == EOF)
//...
if((ticks = clock()) < 0)
//...
-
代码可读性降低
-
质量降级
-
信息有限
char *IntToAscii(int dwVal, char *pszRes, int dwRadix)
{
if(NULL == pszRes)
return "Arg2Null";
if((dwRadix < 2) || (dwRadix > 36))
return "Arg3OutOfRange";
//...
return pszRes;
}
-
定义冲突
-
无约束性
typedef enum{
S_OK, //成功
S_ERROR, //失败(原因未明确),通用状态
S_NULL_POINTER, //入参指针为NULL
S_ILLEGAL_PARAM, //参数值非法,通用
S_OUT_OF_RANGE, //参数值越限
S_MAX_STATUS //不可作为返回值状态,仅作枚举最值使用
}FUNC_STATUS;
((eRetCode) == S_OK ? : \
((eRetCode) == S_ERROR ? : \
((eRetCode) == S_NULL_POINTER ? : \
((eRetCode) == S_ILLEGAL_PARAM ? : \
((eRetCode) == S_OUT_OF_RANGE ? : \
)))))
2.2 全局状态标志(errno)
extern int errno;
extern int *__errno_location(void);
-
函数返回成功时,允许其修改errno。
//调用库函数
if(返回错误值)
//检查errno
-
库函数返回失败时,不一定会设置errno,取决于具体的库函数。 -
errno在程序开始时设置为0,任何库函数都不会将errno再次清零。
-
使用errno前,应避免调用其他可能设置errno的库函数。如:
if (somecall() == -1)
{
printf("somecall() failed\n");
if(errno == ...) { ... }
}
if (somecall() == -1)
{
int dwErrSaved = errno;
printf("somecall() failed\n");
if(dwErrSaved == ...) { ... }
}
-
使用现代版本的C库时,应包含使用 头文件;在非常老的Unix 系统中,可能没有该头文件,此时可手工声明errno(如extern int errno)。
char *strerror(int errnum);
void perror(const char *msg);
int main(int argc, char** argv)
{
errno = 0;
FILE *pFile = fopen(argv[1], "r");
if(NULL == pFile)
{
printf("Cannot open file '%s'(%s)!\n", argv[1], strerror(errno));
perror("Open file failed");
}
else
{
printf("Open file '%s'(%s)!\n", argv[1], strerror(errno));
perror("Open file");
fclose(pFile);
}
return 0;
}
[wangxiaoyuan_@localhost test1]$ ./GlbErr /sdb1/wangxiaoyuan/linux_test/test1/test.c
Open file '/sdb1/wangxiaoyuan/linux_test/test1/test.c'(Success)!
Open file: Success
[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h
Cannot open file 'NonexistentFile.h'(No such file or directory)!
Open file failed: No such file or directory
[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h > test
Open file failed: No such file or directory
[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h 2> test
Cannot open file 'NonexistentFile.h'(No such file or directory)!
int *_fpErrNo(void)
{
static int dwLocalErrNo = 0;
return &dwLocalErrNo;
}
//define other error macros...
int Callee(void)
{
ErrNo = 1;
return -1;
}
int main(void)
{
ErrNo = 0;
if((-1 == Callee()) && (EOUTOFRANGE == ErrNo))
printf("Callee failed(ErrNo:%d)!\n", ErrNo);
return 0;
}
2.3 局部跳转(goto)
double Division(double fDividend, double fDivisor)
{
return fDividend/fDivisor;
}
int main(void)
{
int dwFlag = 0;
if(1 == dwFlag)
{
RaiseException:
printf("The divisor cannot be 0!\n");
exit(1);
}
dwFlag = 1;
double fDividend = 0.0, fDivisor = 0.0;
printf("Enter the dividend: ");
scanf("%lf", &fDividend);
printf("Enter the divisor : ");
scanf("%lf", &fDivisor);
if(0 == fDivisor) //不太严谨的浮点数判0比较
goto RaiseException;
printf("The quotient is %.2lf\n", Division(fDividend, fDivisor));
return 0;
}
[ ]$ ./test
Enter the dividend: 10
Enter the divisor : 0
The divisor cannot be 0!
[ ]$ ./test
Enter the dividend: 10
Enter the divisor : 2
The quotient is 5.00
虽然goto语句会破坏代码结构性,但却非常适用于集中错误处理。伪代码示例如下:
CallerFunc()
{
if((ret = CalleeFunc1()) < 0);
goto ErrHandle;
if((ret = CalleeFunc2()) < 0);
goto ErrHandle;
if((ret = CalleeFunc3()) < 0);
goto ErrHandle;
//...
return;
ErrHandle:
//Handle Error(e.g. printf)
return;
}
2.4 非局部跳转(setjmp/longjmp)
int setjmp(jmp_buf env);
void longjmp(jmp_buf env,int val);
jmp_buf gJmpBuf;
void Func1(){
printf("Enter Func1\n");
if(0)longjmp(gJmpBuf, 1);
}
void Func2(){
printf("Enter Func2\n");
if(0)longjmp(gJmpBuf, 2);
}
void Func3(){
printf("Enter Func3\n");
if(1)longjmp(gJmpBuf, 3);
}
int main(void)
{
int dwJmpRet = setjmp(gJmpBuf);
printf("dwJmpRet = %d\n", dwJmpRet);
if(0 == dwJmpRet)
{
Func1();
Func2();
Func3();
}
else
{
switch(dwJmpRet)
{
case 1:
printf("Jump back from Func1\n");
break;
case 2:
printf("Jump back from Func2\n");
break;
case 3:
printf("Jump back from Func3\n");
break;
default:
printf("Unknown Func!\n");
break;
}
}
return 0;
}
dwJmpRet = 0
Enter Func1
Enter Func2
Enter Func3
dwJmpRet = 3
Jump back from Func3
jmp_buf gJmpBuf;
void RaiseException(void)
{
printf("Exception is raised: ");
longjmp(gJmpBuf, 1); //throw,跳转至异常处理代码
printf("This line should never get printed!\n");
}
double Division(double fDividend, double fDivisor)
{
return fDividend/fDivisor;
}
int main(void)
{
double fDividend = 0.0, fDivisor = 0.0;
printf("Enter the dividend: ");
scanf("%lf", &fDividend);
printf("Enter the divisor : ");
if(0 == setjmp(gJmpBuf)) //try块
{
scanf("%lf", &fDivisor);
if(0 == fDivisor) //也可将该判断及RaiseException置于Division内
RaiseException();
printf("The quotient is %.2lf\n", Division(fDividend, fDivisor));
}
else //catch块(异常处理代码)
{
printf("The divisor cannot be 0!\n");
}
return 0;
}
Enter the dividend: 10
Enter the divisor : 0
Exception is raised: The divisor cannot be 0!
-
必须先调用setjmp()函数后调用longjmp()函数,以恢复到先前被保存的程序执行点。若调用顺序相反,将导致程序的执行流变得不可预测,很容易导致程序崩溃。 -
longjmp()函数必须在setjmp()函数的作用域之内。在调用setjmp()函数时,它保存的程序执行点环境只在当前主调函数作用域以内(或以后)有效。若主调函数返回或退出到上层(或更上层)的函数环境中,则setjmp()函数所保存的程序环境也随之失效(函数返回时堆栈内存失效)。这就要求setjmp()不可该封装在一个函数中,若要封装则必须使用宏(详见《C语言接口与实现》“第4章 异常与断言”)。 -
通常将jmp_buf变量定义为全局变量,以便跨函数调用longjmp。 -
通常,存放在存储器中的变量将具有longjmp时的值,而在CPU和浮点寄存器中的变量则恢复为调用setjmp时的值。因此,若在调用setjmp和longjmp之间修改自动变量或寄存器变量的值,当setjmp从longjmp调用返回时,变量将维持修改后的值。若要编写使用非局部跳转的可移植程序,必须使用volatile属性。 -
使用异常机制不必每次调用都检查一次返回值,但因为程序中任何位置都可能抛出异常,必须时刻考虑是否捕捉异常。在大型程序中,判断是否捕捉异常会是很大的思维负担,影响开发效率。相比之下,通过返回值指示错误有利于调用者在最近出错的地方进行检查。此外,返回值模式中程序的运行顺序一目了然,对维护者可读性更高。因此,应用程序中不建议使用setjmp/longjmp“异常处理”机制(除非库或框架)。
2.5 信号(signal/raise)
typedef void (*fpSigFunc)(int);
fpSigFunc signal(int signo, fpSigFunc fpHandler);
int raise(int signo);
void fphandler(int dwSigNo)
{
printf("Exception is raised, dwSigNo=%d!\n", dwSigNo);
}
int main(void)
{
if(SIG_ERR == signal(SIGFPE, fphandler))
{
fprintf(stderr, "Fail to set SIGFPE handler!\n");
exit(EXIT_FAILURE);
}
double fDividend = 10.0, fDivisor = 0.0;
if(0 == fDivisor)
{
raise(SIGFPE);
exit(EXIT_FAILURE);
}
printf("The quotient is %.2lf\n", fDividend/fDivisor);
return 0;
}
int main(void)
{
if(SIG_ERR == signal(SIGFPE, fphandler))
{
fprintf(stderr, "Fail to set SIGFPE handler!\n");
exit(EXIT_FAILURE);
}
int dwDividend = 10, dwDivisor = 0;
double fQuotient = dwDividend/dwDivisor;
printf("The quotient is %.2lf\n", fQuotient);
return 0;
}
-
将SIGFPE信号变成系统默认处理,即signal(SIGFPE, SIG_DFL)。
-
利用setjmp/longjmp跳过引发异常的指令:
jmp_buf gJmpBuf;
void fphandler(int dwSigNo)
{
printf("Exception is raised, dwSigNo=%d!\n", dwSigNo);
longjmp(gJmpBuf, 1);
}
int main(void)
{
if(SIG_ERR == signal(SIGFPE, SIG_DFL))
{
fprintf(stderr, "Fail to set SIGFPE handler!\n");
exit(EXIT_FAILURE);
}
int dwDividend = 10, dwDivisor = 0;
if(0 == setjmp(gJmpBuf))
{
double fQuotient = dwDividend/dwDivisor;
printf("The quotient is %.2lf\n", fQuotient);
}
else
{
printf("The divisor cannot be 0!\n");
}
return 0;
}
三 错误处理
3.1 终止(abort/exit)
void exit(int status);
void _Exit(int status);
void _exit(int status);
int main(void)
{
printf("Using exit...\n");
printf("This is the content in buffer");
exit(0);
printf("This line will never be reached\n");
}
Using exit...
This is the content in buffer(结尾无换行符)
int main(void)
{
printf("Using _exit...\n");
printf("This is the content in buffer");
fprintf(stdout, "Standard output stream");
fprintf(stderr, "Standard error stream");
//fflush(stdout);
_exit(0);
}
Using _exit...
Standard error stream(结尾无换行符)
Using _exit...
Standard error streamThis is the content in bufferStandard output stream(结尾无换行符)
int atexit(void (*func)(void));
double Division(double fDividend, double fDivisor)
{
return fDividend/fDivisor;
}
void RaiseException1(void)
{
printf("Exception is raised: \n");
}
void RaiseException2(void)
{
printf("The divisor cannot be 0!\n");
}
int main(void)
{
double fDividend = 0.0, fDivisor = 0.0;
printf("Enter the dividend: ");
scanf("%lf", &fDividend);
printf("Enter the divisor : ");
scanf("%lf", &fDivisor);
if(0 == fDivisor)
{
atexit(RaiseException2);
atexit(RaiseException1);
exit(EXIT_FAILURE);
}
printf("The quotient is %.2lf\n", Division(fDividend, fDivisor));
return 0;
}
Enter the dividend: 10
Enter the divisor : 0
Exception is raised:
The divisor cannot be 0!
#include
void abort(void);
void abort(void)
{
raise(SIGABRT);
exit(EXIT_FAILURE);
}
3.2 断言(assert)
extern void __assert((const char *, const char *, int, const char *));
((void) ((expr) || \
(__assert(
void __assert(const char *assertion, const char * filename,
int linenumber, register const char * function)
{
fprintf(stderr, " [%s(%d)%s] Assertion '%s' failed.\n",
filename, linenumber,
((function == NULL) ? "UnknownFunc" : function),
assertion);
abort();
}
(fprintf(stderr, "[%s(%d)] Assertion '%s' failed.\n", \
__FILE__, __LINE__,
-
断言用于检测理论上绝不应该出现的情况,如入参指针为空、除数为0等。
char *Strcpy(char *pszDst, const char *pszSrc)
{
char *pszDstOrig = pszDst;
assert((pszDst != NULL) && (pszSrc != NULL));
while((*pszDst++ = *pszSrc++) != '\0');
return pszDstOrig;
}
FILE *OpenFile(const char *pszName, const char *pszMode)
{
FILE *pFile = fopen(pszName, pszMode);
assert(pFile != NULL);
if(NULL == pFile)
return NULL;
//...
return pFile;
}
int main(void)
{
int dwChg = 0;
assert(dwChg = 1);
if(0 == dwChg)
printf("Assertion should be enabled!\n");
return 0;
}
-
不应使用断言检查公共方法的参数(应使用参数校验代码),但可用于检查传递给私有方法的参数。 -
可使用断言测试方法执行的前置条件和后置条件,以及执行前后的不变性。 -
断言条件不成立时,会调用abort()函数终止程序,应用程序没有机会做清理工作(如关闭文件和数据库)。
3.3 封装
-
封装具有错误返回值的函数
pid_t Fork(void) //首字母大写,以区分系统函数fork()
{
pid_t pid;
if((pid = fork())<0)
{
fprintf(stderr, "Fork error: %s\n", strerror(errno));
exit(0);
}
return pid;
}
-
封装错误输出
int daemon_proc; /* set nonzero by daemon_init() */
static void err_doit(int errnoflag, int level, const char * fmt, va_list ap)
{
int errno_save, n;
char buf[MAXLINE + 1];
errno_save = errno; /* Value caller might want printed. */
vsnprintf(buf, MAXLINE, fmt, ap);
vsprintf(buf, fmt, ap); /* This is not safe */
n = strlen(buf);
if (errnoflag) {
snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
}
strcat(buf, "\n");
if (daemon_proc) {
syslog(level, buf);
} else {
fflush(stdout); /* In case stdout and stderr are the same */
fputs(buf, stderr);
fflush(stderr);
}
return;
}
void err_ret(const char * fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, LOG_INFO, fmt, ap);
va_end(ap);
return;
}
作者:clover-toeic
原文:https://www.cnblogs.com/clover-toeic/p/3919857.html
飞机上一般是什么操作系统?
高速CAN、容错CAN、LIN总线有什么区别?
大佬终于把鸿蒙OS讲明白了,收藏了!
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!