C語言的錯誤處理

爲了開發強壯的程序,需要預見程序在執行時可能的錯誤,並對錯誤進行檢測和處理以在錯誤發生時能夠採取適當的行爲。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>中主要的是setjmplongjmp兩個函數,setjmp宏標記程序中的一個位置,隨後又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");
}

非局部跳轉函數

參考文獻

  1. K.N. King 著,呂秀峯 譯. C語言程序設計-現代方法. 人民郵電出版社
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章