前言
一、錯誤概念
1.1 錯誤分類
1.2 處理步驟
-
程序執(zhí)行時發(fā)生軟件錯誤。該錯誤可能產(chǎn)生于被底層驅(qū)動或內(nèi)核映射為軟件錯誤的硬件響應(yīng)事件(如除零)。 -
以一個錯誤指示符(如整數(shù)或結(jié)構(gòu)體)記錄錯誤的原因及相關(guān)信息。 -
程序檢測該錯誤(讀取錯誤指示符,或由其主動上報); -
程序決定如何處理錯誤(忽略、部分處理或完全處理); -
恢復(fù)或終止程序的執(zhí)行。
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 返回值和回傳參數(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)⒅羔槥镹ULL
S_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)標(biāo)志(errno)
extern int errno;
extern int *__errno_location(void);
-
函數(shù)返回成功時,允許其修改errno。
//調(diào)用庫函數(shù)
if(返回錯誤值)
//檢查errno
-
庫函數(shù)返回失敗時,不一定會設(shè)置errno,取決于具體的庫函數(shù)。 -
errno在程序開始時設(shè)置為0,任何庫函數(shù)都不會將errno再次清零。
-
使用errno前,應(yīng)避免調(diào)用其他可能設(shè)置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庫時,應(yīng)包含使用 頭文件;在非常老的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.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 局部跳轉(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;
}
[ ]$ ./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語句會破壞代碼結(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 = 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,跳轉(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: 10
Enter the divisor : 0
Exception is raised: The divisor cannot be 0!
-
必須先調(diào)用setjmp()函數(shù)后調(diào)用longjmp()函數(shù),以恢復(fù)到先前被保存的程序執(zhí)行點。若調(diào)用順序相反,將導(dǎo)致程序的執(zhí)行流變得不可預(yù)測,很容易導(dǎo)致程序崩潰。 -
longjmp()函數(shù)必須在setjmp()函數(shù)的作用域之內(nèi)。在調(diào)用setjmp()函數(shù)時,它保存的程序執(zhí)行點環(huán)境只在當(dāng)前主調(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和浮點寄存器中的變量則恢復(fù)為調(diào)用setjmp時的值。因此,若在調(diào)用setjmp和longjmp之間修改自動變量或寄存器變量的值,當(dāng)setjmp從longjmp調(diào)用返回時,變量將維持修改后的值。若要編寫使用非局部跳轉(zhuǎn)的可移植程序,必須使用volatile屬性。 -
使用異常機制不必每次調(diào)用都檢查一次返回值,但因為程序中任何位置都可能拋出異常,必須時刻考慮是否捕捉異常。在大型程序中,判斷是否捕捉異常會是很大的思維負擔(dān),影響開發(fā)效率。相比之下,通過返回值指示錯誤有利于調(diào)用者在最近出錯的地方進行檢查。此外,返回值模式中程序的運行順序一目了然,對維護者可讀性更高。因此,應(yīng)用程序中不建議使用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: 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__,
-
斷言用于檢測理論上絕不應(yīng)該出現(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;
}
-
不應(yīng)使用斷言檢查公共方法的參數(shù)(應(yīng)使用參數(shù)校驗代碼),但可用于檢查傳遞給私有方法的參數(shù)。 -
可使用斷言測試方法執(zhí)行的前置條件和后置條件,以及執(zhí)行前后的不變性。 -
斷言條件不成立時,會調(diào)用abort()函數(shù)終止程序,應(yīng)用程序沒有機會做清理工作(如關(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講明白了,收藏了!
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!