爲了開發強壯的程序,需要預見程序在執行時可能的錯誤,並對錯誤進行檢測和處理以在錯誤發生時能夠採取適當的行爲。C語言提供了幾個處理錯誤的函數庫。
斷言<assert.h>
C語言使用斷言監控程序的行爲,斷言函數聲明在<assert.h>
中,原型爲:
void assert(int expression);
assert
函數是以宏的形式實現的。參數expression
是一個在正常情況下應該爲真的表達式。在執行assert
函數時,會檢測expression
的值,如果結果爲0(爲假),則assert
函數會向標準錯誤流輸出斷言失敗的信息(包括斷言函數的參數、調用斷言函數的文件名和斷言函數所在行號等信息),並調用abort
函數終止程序。
assert
由於引入了額外的檢查,會增加程序的運行時間,有時這是難以接受的,因此可以在程序發佈時禁止assert
調用。只需要在包含<assert.h>
前定義宏NDEBUG
即可禁止assert
調用。
由於assert
函數可以被禁止調用,因此不要在assert
函數中包含影響程序行爲的內容。
/**************************************
* using_assert.c *
* *
* C語言的錯誤檢測機制:斷言 *
**************************************/
#include <stdio.h>
#include <assert.h>
int main()
{
int x;
printf("請輸入一個正整數:");
scanf("%d", &x);
assert(x > 0);
printf("輸入的數符合要求!\n");
return 0;
}
錯誤碼<errno.h>
標準庫中的一些函數在運行時出現錯誤會向<errno.h>
中的 errno
變量聲明一個錯誤碼。 如果要根據errno
判斷這些函數是否發生了錯誤,在調用前需要先將errno
清零。 errno
中存儲的值通常爲兩種,一種是EDOM
,定義域錯誤,當傳遞給函數的某個參數的值不輸入函數的定義域;第二個是ERANGE
,取值範圍錯誤,當函數的返回值過大,超過了函數的返回值範圍時發生。
<errno.h>
中提供了兩種函數來輸出錯誤信息,其原型分別爲:
void perror(const char *s);
char *strerror(int enum);
perror
函數聲明在<stdio.h>
中,它輸出的信息的格式如下:
s: 出錯信息
依次輸出參數字符串s
,冒號,空格,根據errno
確定的錯誤信息和換行符。
strerror
在<string.h>
中聲明,它根據errno
中的錯誤碼生成描述這種錯誤的字符串。
/**************************************
* using_errno.c *
* *
* C語言中的錯誤碼errno及相關函數的用 *
* 法 *
**************************************/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <math.h>
int main()
{
errno = 0;
sqrt(-1);
perror("開平方參數錯誤");
puts(strerror(EDOM));
puts(strerror(errno));
return 0;
}
信號處理
C語言利用信號(signal
)處理異常情況。信號有兩種類型,程序運行時發生的錯誤或程序外部觸發的事件。當有錯誤或外部事件發生時,就稱產生了一個信號。大多數信號是異步的,可以在程序執行過程中的任何時刻發生,必須採用統一的方式處理它們。
信號宏
在<signal.h>
中定義了一系列宏,用於表示不同的信號。
宏名 | 含義 |
---|---|
SIGABRT |
異常終止,可能由於調用abort引起 |
SIGFPE |
在數學計算中發生錯誤引起(例如除0或溢出等) |
SIGILL |
非法指令 |
SIGINT |
中斷 |
SIGSEGV |
非法存儲訪問 |
SIGTERM |
終止請求 |
C語言的實現可以提供更多的信號宏,只要在宏的名字以SIG
開頭並全用大寫字母組成。在不同的計算機系統上表中的信號並不全有意義。
信號處理函數
在<signal.h>
中有一個signal
函數,該函數的用處是爲不同的信號安裝信號處理函數,以便在信號發生的時候調用。其原型爲:
void (*signal(int sig, void (*func)(int)))(int);
signal
的返回值是前一個信號處理函數的指針。
每個信號處理函數都必須有一個int
類型的參數,當信號發生時,信號的代碼以參數的信息傳入信號處理函數。
除非信號是由abort
函數或raise
函數引起,否則在信號處理函數中不要使用任何庫函數或者使用靜態存儲變量。
不同的信號在信號處理函數執行完成後有不同的結果,一種是返回原函數繼續執行,一種是終止,一種是未定義。
C語言在<signal.h>
中提供了預定義的處理函數,都是用宏表示的,分別是:
- `SIG_DFL`。以默認方式處理信號,`SIG_DFL`的結果是由實現定義的,大部分情況會導致程序終止。
-` SIG_IGN`。當信號發生時,忽略該信號。
<signal.h>
還提供了另一個宏SIG_ERR
,它用以在安裝信號處理函數時是否發生錯誤的宏,如果安裝失敗,就會返回SIG_ERR
的值,並在errno
中寫入一個正值。
爲了避免遞歸,在調用處理函數時,程序在後臺將該信號對應的信號處理函數重置爲SIG_DFL
,或者以其他方式封鎖。
raise
函數
除了信號在特定條件下自動產生外,也可以 使用raise
函數觸發信號 ,該函數在<signal.h>
中聲明,raise
函數的原型爲:
int raise(int sig);
返回值說明調用是否成功, 0表示成功,否則失敗。
/**************************************
* using_signal.c *
* *
* 使用C語言中的信號 *
**************************************/
#include <signal.h>
#include <stdio.h>
void Handler(int sig)
{
printf("Handler called for signal %d\n", sig);
}
void RaiseSignal()
{
raise(SIGILL);
}
int main()
{
void (*orig_handler)(int);
printf("Installing handler for signal %d\n", SIGILL);
orig_handler = signal(SIGILL, Handler);
RaiseSignal();
printf("Change handler to SIG_IGN\n");
signal(SIGILL, SIG_IGN);
RaiseSignal();
printf("Restoring original handler\n");
signal(SIGILL, orig_handler);
RaiseSignal();
printf("Program terminates normally!\n");
return 0;
}
非局部跳轉
通常情況下,函數在執行完後會返回到調用它的地方繼續執行,如果想要由函數直接跳轉到另外一個函數, 則可以使用<setjmp.h>
中提供的非局部跳轉函數。goto
只能跳轉到同一函數內的標記處。
<setjmp.h>
中主要的是setjmp
和longjmp
兩個函數,setjm
p宏標記程序中的一個位置,隨後又longjmp
跳轉到這個位置。這一機制主要用以錯誤處理。這兩個函數的原型爲:
int setjmp(jmpbuf env);
void longjmp(jmp_buf env, int val);
setjmp
宏調用的參數爲jmp_buff
類型,同樣在<setjmp.h>
中定義。setjmp
將當前的環境(包括指向當前繪製的指針)保存到變量中以便在longjmp
中調用,返回0.
使用longjmp
宏回到setjmp
宏標記的地方,調用的參數是與setjmp
同一個jmp_buf
變量,longjmp
首先根據jmp_buf
恢復環境,然後從setjmp
中返回,返回的值就是val
的值(如果val
爲0,則setjmp
返回1)。val
實際上是發生跳轉的位置的標誌,可以根據該值判斷當前代碼的執行是從哪個longjmp
跳轉過來的。
/*************************************
* using_setjmp.c *
* *
* 使用C語言的非局部跳轉函數,例子來 *
* 源於C語言程序設計-現代方法 *
*************************************/
#include <setjmp.h>
#include <stdio.h>
static jmp_buf env;
void f1(void);
void f2(void);
int main()
{
int ret;
ret = setjmp(env);
printf("setjmp returned %d\n", ret);
if (ret != 0)
{
printf("Program terminates: longjmp called\n");
return 0;
}
f1();
printf("Program terminates normally\n");
return 0;
}
void f1(void)
{
printf("f1 begins\n");
f2();
printf("f2 returns");
}
void f2(void)
{
printf("f2 begins\n");
longjmp(env, 1);
printf("f2 returns\n");
}
參考文獻
- K.N. King 著,呂秀峯 譯. C語言程序設計-現代方法. 人民郵電出版社