前言
一、錯誤概念
1.1 錯誤分類
1.2 處理步驟
-
程序執(zhí)行時發(fā)生軟件錯誤。該錯誤可能產(chǎn)生于被底層驅(qū)動或內(nèi)核映射為軟件錯誤的硬件響應事件(如除零)。 -
以一個錯誤指示符(如整數(shù)或結(jié)構(gòu)體)記錄錯誤的原因及相關(guān)信息。 -
程序檢測該錯誤(讀取錯誤指示符,或由其主動上報); -
程序決定如何處理錯誤(忽略、部分處理或完全處理); -
恢復或終止程序的執(zhí)行。
int func(){int bIsErrOccur = 0;//do something that might invoke errorsif(bIsErrOccur) //Stage 1: error occurredreturn -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 abortreturn 0;}
二 、錯誤傳遞
2.1 返回值和回傳參數(shù)
if((p = malloc(100)) == NULL)//...if((c = getchar()) == EOF)//...if((ticks = clock()) < 0)//...
-
代碼可讀性降低
-
質(zhì)量降級
-
信息有限
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, //失敗(原因未明確),通用狀態(tài)S_NULL_POINTER, //入?yún)⒅羔槥镹ULLS_ILLEGAL_PARAM, //參數(shù)值非法,通用S_OUT_OF_RANGE, //參數(shù)值越限S_MAX_STATUS //不可作為返回值狀態(tài),僅作枚舉最值使用}FUNC_STATUS;((eRetCode) == S_OK ? : \((eRetCode) == S_ERROR ? : \((eRetCode) == S_NULL_POINTER ? : \((eRetCode) == S_ILLEGAL_PARAM ? : \((eRetCode) == S_OUT_OF_RANGE ? : \)))))
2.2 全局狀態(tài)標志(errno)
extern int errno;
extern int *__errno_location(void);
-
函數(shù)返回成功時,允許其修改errno。
//調(diào)用庫函數(shù)if(返回錯誤值)//檢查errno
-
庫函數(shù)返回失敗時,不一定會設置errno,取決于具體的庫函數(shù)。 -
errno在程序開始時設置為0,任何庫函數(shù)都不會將errno再次清零。
-
使用errno前,應避免調(diào)用其他可能設置errno的庫函數(shù)。如:
if (somecall() == -1){printf("somecall() failed\n");if(errno == ...) { ... }}
if (somecall() == -1){int dwErrSaved = errno;printf("somecall() failed\n");if(dwErrSaved == ...) { ... }}
-
使用現(xiàn)代版本的C庫時,應包含使用 頭文件;在非常老的Unix 系統(tǒng)中,可能沒有該頭文件,此時可手工聲明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.cOpen file '/sdb1/wangxiaoyuan/linux_test/test1/test.c'(Success)!Open file: Success[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.hCannot open file 'NonexistentFile.h'(No such file or directory)!Open file failed: No such file or directory[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h > testOpen file failed: No such file or directory[wangxiaoyuan_@localhost test1]$ ./GlbErr NonexistentFile.h 2> testCannot 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 局部跳轉(zhuǎn)(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) //不太嚴謹?shù)母↑c數(shù)判0比較goto RaiseException;printf("The quotient is %.2lf\n", Division(fDividend, fDivisor));return 0;}
[]$ ./testEnter the dividend: 10Enter the divisor : 0The divisor cannot be 0![]$ ./testEnter the dividend: 10Enter the divisor : 2The quotient is 5.00雖然goto語句會破壞代碼結(jié)構(gòu)性,但卻非常適用于集中錯誤處理。偽代碼示例如下: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 非局部跳轉(zhuǎn)(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 = 0Enter Func1Enter Func2Enter Func3dwJmpRet = 3Jump back from Func3
jmp_buf gJmpBuf;void RaiseException(void){printf("Exception is raised: ");longjmp(gJmpBuf, 1); //throw,跳轉(zhuǎn)至異常處理代碼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內(nèi)RaiseException();printf("The quotient is %.2lf\n", Division(fDividend, fDivisor));}else //catch塊(異常處理代碼){printf("The divisor cannot be 0!\n");}return 0;}
Enter the dividend: 10Enter the divisor : 0Exception is raised: The divisor cannot be 0!
-
必須先調(diào)用setjmp()函數(shù)后調(diào)用longjmp()函數(shù),以恢復到先前被保存的程序執(zhí)行點。若調(diào)用順序相反,將導致程序的執(zhí)行流變得不可預測,很容易導致程序崩潰。 -
longjmp()函數(shù)必須在setjmp()函數(shù)的作用域之內(nèi)。在調(diào)用setjmp()函數(shù)時,它保存的程序執(zhí)行點環(huán)境只在當前主調(diào)函數(shù)作用域以內(nèi)(或以后)有效。若主調(diào)函數(shù)返回或退出到上層(或更上層)的函數(shù)環(huán)境中,則setjmp()函數(shù)所保存的程序環(huán)境也隨之失效(函數(shù)返回時堆棧內(nèi)存失效)。這就要求setjmp()不可該封裝在一個函數(shù)中,若要封裝則必須使用宏(詳見《C語言接口與實現(xiàn)》“第4章 異常與斷言”)。 -
通常將jmp_buf變量定義為全局變量,以便跨函數(shù)調(diào)用longjmp。 -
通常,存放在存儲器中的變量將具有l(wèi)ongjmp時的值,而在CPU和浮點寄存器中的變量則恢復為調(diào)用setjmp時的值。因此,若在調(diào)用setjmp和longjmp之間修改自動變量或寄存器變量的值,當setjmp從longjmp調(diào)用返回時,變量將維持修改后的值。若要編寫使用非局部跳轉(zhuǎn)的可移植程序,必須使用volatile屬性。 -
使用異常機制不必每次調(diào)用都檢查一次返回值,但因為程序中任何位置都可能拋出異常,必須時刻考慮是否捕捉異常。在大型程序中,判斷是否捕捉異常會是很大的思維負擔,影響開發(fā)效率。相比之下,通過返回值指示錯誤有利于調(diào)用者在最近出錯的地方進行檢查。此外,返回值模式中程序的運行順序一目了然,對維護者可讀性更高。因此,應用程序中不建議使用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信號變成系統(tǒng)默認處理,即signal(SIGFPE, SIG_DFL)。
-
利用setjmp/longjmp跳過引發(fā)異常的指令:
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(結(jié)尾無換行符)
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(結(jié)尾無換行符)
Using _exit...Standard error streamThis is the content in bufferStandard output stream(結(jié)尾無換行符)
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: 10Enter the divisor : 0Exception is raised:The divisor cannot be 0!
#includevoid 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__,
-
斷言用于檢測理論上絕不應該出現(xiàn)的情況,如入?yún)⒅羔槥榭?、除?shù)為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;}
-
不應使用斷言檢查公共方法的參數(shù)(應使用參數(shù)校驗代碼),但可用于檢查傳遞給私有方法的參數(shù)。 -
可使用斷言測試方法執(zhí)行的前置條件和后置條件,以及執(zhí)行前后的不變性。 -
斷言條件不成立時,會調(diào)用abort()函數(shù)終止程序,應用程序沒有機會做清理工作(如關(guān)閉文件和數(shù)據(jù)庫)。
3.3 封裝
-
封裝具有錯誤返回值的函數(shù)
pid_t Fork(void) //首字母大寫,以區(qū)分系統(tǒng)函數(shù)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
飛機上一般是什么操作系統(tǒng)?
高速CAN、容錯CAN、LIN總線有什么區(qū)別?
大佬終于把鴻蒙OS講明白了,收藏了!
免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!






