1. 程式人生 > >C 語言異常處理(五十二)

C 語言異常處理(五十二)

異常處理 C 中的異常處理 if...else... setjmp() longjmp()

我們今天來看下異常處理,在看 C++ 的異常處理之前,先來看看 C 語言中的異常處理。那麽什麽是異常呢?在程序運行過程中可能會產生異常,異常(Exception)與 Bug 的區別是:異常是程序運行時可預料的執行分支,而 Bug 是程序中的錯誤,是不被預期的運行方式。

下來我們來看看異常和 Bug 的對比:a> 異常比如運行時產生除 0 的情況,需要打開的外部文件不存在,數組訪問時越界;b> Bug 是使用野指針,堆數組使用結束後未釋放,選擇排序無法處理長度為 0 的數組。在 C 語言中的經典處理方式為:if ... else ... 。if 語句中處理的是正常情況代碼邏輯,else 語句中處理的是異常情況代碼邏輯。

我們還是以代碼為例來看看除法操作異常的處理

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        ret = 0;
    }
    
    return ret;
}

int main()
{
    cout << divide(1, 1) << endl;
    cout << divide(1, 0) << endl;

    return 0;
}

我們看看編譯結果

技術分享圖片

執行的結果是正確的,但是如果我們打印的是 0/1 的結果呢?我們就不知道執行的到底是正確的情況還是異常的情況。那麽我們在上面程序中的 divide 函數中添加一個參數,用來表示執行結果的正確與否,根據這個參數的值來判斷執行是否正常。程序如下

#include <iostream>
#include <string>

using namespace std;

double divide(double a, double b, int* valid)
{
    const double delta = 0.000000000000001;
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
        
        *valid = 1;
    }
    else
    {
        *valid = 0;
    }
    
    return ret;
}

int main()
{
    int valid = 0;
    double r = divide(1, 0, &valid);
    
    if( valid )
    {
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divided by zero..." << endl;
    }

    return 0;
}

我們來看看編譯結果

技術分享圖片

再來試試 0/1 呢

技術分享圖片

我們看到結果已經正確的執行了。但是這個程序有個缺陷,便是 divide 函數需要 3 個參數,難以理解它的用法,而且 divide 函數調用後必須判斷 valid 代表的結果,當 valid 為 true 時,運算結果正常;當 valid 為false 時,運算結果出現異常。在 C 語言還有一種異常處理的方式,通過 setjmp() 和 longjmp() 進行判斷。下來我們來講講這兩個函數的原型及其意思:a> int setjmp(jmp_buf env) 是將當前上下文保存在 jmp_buf 結構體中;b> void longjmp(jmp_buf env, int val) 從 jmp_buf 結構體中恢復 setjmp() 保存的上下文,最終從 setjmp 函數調用點返回,返回值為 val;下來我們通過示例代碼來進行分析

#include <iostream>
#include <string>
#include <csetjmp>

using namespace std;

static jmp_buf env;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;
    
    if( !((-delta < b) && (b < delta)) )
    {
        ret = a / b;
    }
    else
    {
        longjmp(env, 1);
    }
    
    return ret;
}

int main()
{
    if( setjmp(env) == 0 )
    {
        double r = divide(1, 0);
        
        cout << "r = " << r << endl;
    }
    else
    {
        cout << "Divided by zero..." << endl;
    }

    return 0;
}

在它進入 main 函數的 if 語句後,會將 0 保存在當前的 env 中,然後 setjmp 函數會保存當前的上下文信息。然後執行 divide 函數,進入到 divide 函數中,會進入到 else 分支。longjmp 函數則會將 1 保存到 env 中並將程序的執行流跳轉到 setjmp 處,此時 env 為 1,因此條件不成立,所以會打印出 Divided by zero...,我們來看看編譯結果

技術分享圖片

我們再來看看 1/1 呢,進入到 divide 函數中,它會執行 if 語句進行正常的計算之後直接便會返回 ret,便會輸出結果

技術分享圖片

雖然這是兩個參數,但是它有一定的缺陷。setjmp() 和 longjmp() 的引入就必然涉及到使用全局變量,暴力跳轉導致代碼的可讀性降低,其本質還是 if ... else ... 異常處理方式。它的暴力跳轉會破壞 C 語言的結構化特性(順序執行、選擇執行、循環執行)。C 語言中的經典異常處理方式會使得程序中邏輯混入大量的處理異常的代碼。正常邏輯代碼和異常處理代碼混合在一起,導致代碼迅速膨脹,難以維護。那麽在 C++ 中肯定便會有更好的異常處理方式,我們後面會繼續學習。通過對 C 語言中異常處理的學習,總結如下:1、程序中不可避免的會發生異常;2、異常是在開發階段就可以預見的運行時問題;3、C 語言中通過經典的 if ... else ... 方式處理異常,在 C++ 中存在更好的異常處理方式。


歡迎大家一起來學習 C++ 語言,可以加我QQ:243343083

C 語言異常處理(五十二)