跳轉到內容

C 程式設計/錯誤處理

來自華夏公益教科書,開放的書籍,開放的世界
上一頁:記憶體管理 C 程式設計 下一頁:檔案 I/O

C 語言不直接支援錯誤處理(也稱為異常處理)。按照慣例,程式設計師應該首先防止錯誤發生,並測試函式的返回值。例如,-1 和 NULL 分別用於 socket()(Unix 套接字程式設計)或 malloc() 等多個函式,以指示程式設計師應該注意的問題。在最壞的情況下,如果出現不可避免的錯誤並且無法從中恢復,C 程式設計師通常會嘗試記錄錯誤並“優雅地”終止程式。

有一個名為“errno”的外部變數,在包含 <errno.h> 後,程式可以訪問該變數 - 該檔案來自某些作業系統(例如 Linux - 在這種情況下,定義位於 include/asm-generic/errno.h 中)中可能發生的錯誤的定義,當程式請求資源時。此變數索引由函式 'strerror( errno )' 訪問的錯誤描述。

以下程式碼測試庫函式 malloc 的返回值,以檢視動態記憶體分配是否已正確完成

#include <stdio.h>        /* perror */
#include <errno.h>        /* errno */
#include <stdlib.h>       /* malloc, free, exit */

int main(void)
{

    /* Pointer to char, requesting dynamic allocation of 2,000,000,000
     * storage elements (declared as an integer constant of type
     * unsigned long int). (If your system has less than 2 GB of memory
     * available, then this call to malloc will fail.)
     */
    char *ptr = malloc(2000000000UL);

    if (ptr == NULL) {
        perror("malloc failed");
        /* here you might want to exit the program or compensate
           for that you don't have 2GB available
         */
    } else {
        /* The rest of the code hereafter can assume that 2,000,000,000
         * chars were successfully allocated... 
         */
        free(ptr);
    }

    exit(EXIT_SUCCESS); /* exiting program */
}

上面的程式碼片段展示了使用庫函式 malloc 的返回值來檢查錯誤。許多庫函式都有返回值來標記錯誤,因此應該由精明的程式設計師進行檢查。在上面的程式碼片段中,從 malloc 返回的 NULL 指標表示分配錯誤,因此程式退出。在更復雜的實現中,程式可能會嘗試處理錯誤並嘗試從失敗的記憶體分配中恢復。

防止除以零錯誤

[編輯 | 編輯原始碼]

C 程式設計師常犯的一個錯誤是在執行除法命令之前沒有檢查除數是否為零。以下程式碼將產生執行時錯誤,並且在大多數情況下會退出。

int dividend = 50;
int divisor = 0;
int quotient;

quotient = (dividend/divisor); /* This will produce a runtime error! */

在普通算術中,除以零 是未定義的。因此,您必須檢查或確保除數永遠不為零。或者,對於 *nix 程序,您可以透過阻止 SIGFPE 訊號來阻止作業系統終止您的程序。

以下程式碼透過在除法之前檢查除數是否為零來修復此問題。

#include <stdio.h> /* for fprintf and stderr */
#include <stdlib.h> /* for exit */
int main( void )
{
    int dividend = 50;
    int divisor = 0;
    int quotient;

    if (divisor == 0) {
        /* Example handling of this error. Writing a message to stderr, and
         * exiting with failure.
         */
        fprintf(stderr, "Division by zero! Aborting...\n");
        exit(EXIT_FAILURE); /* indicate failure.*/
    }

    quotient = dividend / divisor;
    exit(EXIT_SUCCESS); /* indicate success.*/
}

在某些情況下,環境可能會透過發出訊號來響應 C 語言中的程式設計錯誤。訊號是由主機環境或作業系統發出的事件,表示已發生特定錯誤或嚴重事件(例如除以零、中斷等)。但是,這些訊號並非旨在用作錯誤捕獲的手段;它們通常表示會干擾正常程式流程的嚴重事件。

為了處理訊號,程式需要使用signal.h標頭檔案。需要定義一個訊號處理程式,然後呼叫 signal() 函式以允許處理給定的訊號。一些在程式碼中引發異常的訊號(例如除以零)不太可能讓程式恢復。這些訊號處理程式將需要改為確保在程式終止之前正確清理某些資源。

C 標準庫只定義了六個訊號;Unix 系統定義了另外 15 個。每個訊號都有一個與之關聯的數字,稱為訊號號。

    #define SIGHUP  1   /* Hangup the process */ 
    #define SIGINT  2   /* Interrupt the process. C standard */ 
    #define SIGQUIT 3   /* Quit the process */ 
    #define SIGILL  4   /* Illegal instruction. C standard.*/ 
    #define SIGTRAP 5   /* Trace trap, for debugging. C standard.*/ 
    #define SIGABRT 6   /* Abort. C standard. */
    #define SIGFPE  8   /* Floating Point Error. C standard. */
    #define SIGSEGV 11  /* Memory error. C standard.  */
    #define SIGTERM 15  /* Termination request. C standard. */

訊號使用signal()函式處理,來自signal.h庫。其語法為

void signal(signal_to_catch, signal_handler)

訊號可以用raise()kill(). raise()發出訊號給當前程序;kill()發出訊號給特定程序。

請注意signal現在已被棄用,建議使用sigaction(),因為 Unix 系統之間缺乏可移植性以及可能出現意外行為。但是,由於sigaction()的使用更加複雜,我們將堅持使用signal()來在此處說明概念。

為了理解訊號的工作原理,這裡有一個簡單的示例

#include <stdio.h>
#include <unistd.h> // Unix Standard library, used to import sleep()
#include <stdlib.h>
#include <signal.h>

void handler(int signum) {
   printf("Signal received %d, coming out...\n", signum);
   exit(1);
}


int main () {
   signal(SIGINT, handler); // attaching the handler() function to SIGINT signals; i.e, ctrl+c, keyboard interrupt.

   while(1) {
      printf("Sleeping...\n");
      sleep(1000); // sleep pauses the process for a given number of seconds, or until a signal is received. 
   }
   return(0);
}

嘗試在您的機器上編譯並測試此程式碼;在您看到“Sleeping...”後,透過按下ctrl + c.

傳送中斷訊號

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

static void catch_function(int signal) {
    puts("Interactive attention signal caught.");
}

int main(void) {
    if (signal(SIGINT, catch_function) == SIG_ERR) {
        fputs("An error occurred while setting a signal handler.\n", stderr);
        return EXIT_FAILURE;
    }
    puts("Raising the interactive attention signal.");
    if (raise(SIGINT) != 0) {
        fputs("Error raising the signal.\n", stderr);
        return EXIT_FAILURE;
    }
    puts("Exiting.");
    return 0;
}

這裡有一個更復雜的示例。這將建立一個訊號處理程式併發出訊號

setjmp

[編輯 | 編輯原始碼]

這裡有一個更復雜的示例。這將建立一個訊號處理程式併發出訊號可以使用 setjmp 函式來模擬其他程式語言的異常處理功能。對 setjmp 的第一次呼叫儲存對當前執行點的引用點,並且只要包含 setjmp() 的函式不返回或退出,該引用點就有效。對 longjmp 的呼叫會導致執行返回到關聯的 setjmp 呼叫的點。0將一個 `jmp_buf`(將儲存執行上下文的型別)作為引數,並在第一次執行時返回(即,當它設定返回值時)。當它第二次執行時 - 當longjmp(即,當它設定返回值時)。當它第二次執行時 - 當.

(即,當它設定返回值時)。當它第二次執行時 - 當被呼叫時 - 然後它返回傳遞給這裡有一個更復雜的示例。這將建立一個訊號處理程式併發出訊號的值。將一個 `jmp_buf`(已經傳遞給這裡有一個更復雜的示例。這將建立一個訊號處理程式併發出訊號)和一個傳遞給

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

int main(void) {
   int val;
   jmp_buf environment;

   val = setjmp(environment); // val is set to 0 the first time this is called

   if (val !=0) 
   {
      printf("You returned from a longjmp call, return value is %d", val); // now, value is 1, passed from longjmp()
      exit(0);
   }

   puts("Calling longjmp now");
   longjmp(environment, 1);

   return(0);
}

的值作為引數。

嘗試在您自己的機器上使用編譯器執行此程式碼。

當 setjmp 從 longjmp 呼叫返回時,非易失變數的值可能會被破壞。

上一頁:記憶體管理 C 程式設計 下一頁:檔案 I/O
雖然 setjmp() 和 longjmp() 可用於錯誤處理,但通常建議使用函式的返回值來指示錯誤(如果可能)。當錯誤發生在深層巢狀的函式呼叫中時,setjmp() 和 longjmp() 最有用,並且一直檢查返回值到您希望返回的點將很繁瑣。
華夏公益教科書