赞
踩
作者:clover-toeic
原文:https://www.cnblogs.com/clover-toeic/p/3919857.html
本文主要总结嵌入式系统C语言编程中,主要的错误处理方式。文中涉及的代码运行环境如下:
从严重性而言,程序错误可分为致命性和非致命性两类。对于致命性错误,无法执行恢复动作,最多只能在用户屏幕上打印出错消息或将其写入日志文件,然后终止程序;而对于非致命性错误,多数本质上是暂时的(如资源短缺),一般恢复动作是延迟一些时间后再次尝试。
从交互性而言,程序错误可分为用户错误和内部错误两类。用户错误呈现给用户,通常指明用户操作上的错误;而程序内部错误呈现给程序员(可能携带用户不可接触的数据细节),用于查错和排障。
应用程序开发者可决定恢复哪些错误以及如何恢复。例如,若磁盘已满,可考虑删除非必需或已过期的数据;若网络连接失败,可考虑短时间延迟后重建连接。选择合理的错误恢复策略,可避免应用程序的异常终止,从而改善其健壮性。
错误处理即处理程序运行时出现的任何意外或异常情况。典型的错误处理包含五个步骤:
程序执行时发生软件错误。该错误可能产生于被底层驱动或内核映射为软件错误的硬件响应事件(如除零)。
以一个错误指示符(如整数或结构体)记录错误的原因及相关信息。
程序检测该错误(读取错误指示符,或由其主动上报);
程序决定如何处理错误(忽略、部分处理或完全处理);
恢复或终止程序的执行。
上述步骤用C语言代码表述如下:
- 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;}
调用者可能希望函数返回成功时表示完全成功,失败时程序恢复到调用前的状态(但被调函数很难保证这点)。
C语言通常使用返回值来标志函数是否执行成功,调用者通过if等语句检查该返回值以判断函数执行情况。常见的几种调用形式如下:
if((p = malloc(100)) == NULL) //...if((c = getchar()) == EOF) //...if((ticks = clock()) 0) //...
Unix系统调用级函数(和一些老的Posix函数)的返回值有时既包括错误代码也包括有用结果。因此,上述调用形式可在同一条语句中接收返回值并检查错误(当执行成功时返回合法的数据值)。
返回值方式的好处是简便和高效,但仍存在较多问题:
没有返回值的函数是不可靠的。但若每个函数都具有返回值,为保持程序健壮性,就必须对每个函数进行正确性验证,即调用时检查其返回值。这样,代码中很大一部分可能花费在错误处理上,且排错代码和正常流程代码搅在一起,比较混乱。
条件语句相比其他类型的语句潜藏更多的错误。不必要的条件语句会增加排障和白盒测试的工作量。
通过返回值只能返回一个值,因此一般只能简单地标志成功或失败,而无法作为获知具体错误信息的手段。通过按位编码可变通地返回多个值,但并不常用。字符串处理函数可参考IntToAscii()来返回具体的错误原因,并支持链式表达:
- char *IntToAscii(int dwVal, char *pszRes, int dwRadix){
- if(NULL == pszRes) return "Arg2Null"; if((dwRadix 2) || (dwRadix > 36)) return "Arg3OutOfRange"; //... return pszRes;}
不同函数在成功和失败时返回值的取值规则可能不同。例如,Unix系统调用级函数返回0代表成功,-1代表失败;新的Posix函数返回0代表成功,非0代表失败;标准C库中isxxx函数返回1表示成功,0表示失败。
调用者可以忽略和丢弃返回值。未检查和处理返回值时,程序仍然能够运行,但结果不可预知。
新的Posix函数返回值只携带状态和异常信息,并通过参数列表中的指针回传有用的结果。回传参数绑定到相应的实参上,因此调用者不可能完全忽略它们。通过回传参数(如结构体指针)可返回多个值,也可携带更多的信息。
综合返回值和回传参数的优点,可对Get类函数采用返回值(含有用结果)方式,而对Set类函数采用返回值+回传参数方式。对于纯粹的返回值,可按需提供如下解析接口:
- typedef enum{
- S_OK, //成功 S_ERROR, //失败(原因未明确),通用状态 S_NULL_POINTER, //入参指针为NULL S_ILLEGAL_PARAM, //参数值非法,通用 S_OUT_OF_RANGE, //参数值越限 S_MAX_STATUS //不可作为返回值状态,仅作枚举最值使用}FUNC_STATUS;#define RC_NAME(eRetCode) \ ((eRetCode) == S_OK ? "Success" : \ ((eRetCode) == S_ERROR ? "Failure" : \ ((eRetCode) == S_NULL_POINTER ? "NullPointer" : \ ((eRetCode) == S_ILLEGAL_PARAM ? "IllegalParas" : \ ((eRetCode) == S_OUT_OF_RANGE ? "OutOfRange" : \"Unknown")))))
当返回值错误码来自下游模块时,可能与本模块错误码冲突。此时,建议不要将下游错误码直接向上传递,以免引起混乱。若允许向终端或文件输出错误信息,则可详细记录出错现场(如函数名、错误描述、参数取值等),并转换为本模块定义的错误码再向上传递。
Unix系统调用或某些C标准库函数出错时,通常返回一个负值,并设置全局整型变量errno为一个含有错误信息的值。例如,open函数出错时返回-1,并设置errno为EACESS(权限不足)等值。
C标准库头文件中定义errno及其可能的非零常量取值(以字符'E'开头)。在ANSI C中已定义一些基本的errno常量,操作系统也会扩展一部分(但其对错误描述仍显匮乏)。
Linux系统中,出错常量在errno(3)手册页中列出,可通过man 3 errno命令查看。除EAGAIN和EWOULDBLOCK取值相同外,POSIX.1指定的所有出错编号取值均不同。
Posix和ISO C将errno定义为一个可修改的整型左值(lvalue),可以是包含出错编号的一个整数,或是一个返回出错编号指针的函数。以前使用的定义为:
extern int errno;
但在多线程环境中,多个线程共享进程地址空间,每个线程都有属于自己的局部errno(thread-local)以避免一个线程干扰另一个线程。例如,Linux支持多线程存取errno,将其定义为:
extern int *__errno_location(void);#define errno (*__errno_location())
函数__errno_location在不同的库版本下有不同的定义,在单线程版本中,直接返回全局变量errno的地址;而在多线程版本中,不同线程调用__errno_location返回的地址则各不相同。
C运行库中主要在math.h(数学运算)和stdio.h(I/O操作)头文件声明的函数中使用errno。
使用errno时应注意以下几点:
例如,调用fopen函数新建文件时,内部可能会调用其他库函数检测是否存在同名文件。而用于检测文件的库函数在文件不存在时,可能会失败并设置errno。这样, fopen函数每次新建一个事先并不存在的文件时,即使没有
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。