C 幾種異常機制簡單講述

来源:http://www.cnblogs.com/life2refuel/archive/2016/04/03/5349029.html
-Advertisement-
Play Games

引言 這是關於C中如何使用異常機制的討論.順帶講一講C中魔法函數的setjmp內部機制.通過它實現高級的異常try...catch. 允許我先扯一段面試題. 對於電腦面試題. 演算法題等.覺得還是有意義的. 在你封裝基礎庫的時候會簡單用的.因為大家都會得你也會那是及格.如果你想及格+1的話... 開 ...


引言

     這是關於C中如何使用異常機制的討論.順帶講一講C中魔法函數的setjmp內部機制.
通過它實現高級的異常try...catch. 允許我先扯一段面試題. 對於電腦面試題. 演算法題等.覺得還是有意義的.

在你封裝基礎庫的時候會簡單用的.因為大家都會得你也會那是及格.如果你想及格+1的話...

開始吧,題目是這樣的

/*
 *  面試問題 假如有數組 int a[] = {1,2,3,4,5,6,7}, n = 7表示長度, k = 2表示移動步長
 * 移動結果變成 1234567=> 6712345. 
 * 同樣假如 k=3移動三步 1234567=> 5671234
 * 實現一個移動函數
 * void convert(int a[], int n, int k);
 */

的時候基本基本說演算法. 這裡主要想通過這個問題引導異常機制上來.直接貼代碼

// 數組移動函數
void 
convert(int a[], int n, int k) {
    int t=1, i, j;// t是為了數據記錄終止條件的
    // 前面兩個是沒有移動的必要, 後面是已經整除形成周期了也不需要移動
    if ((!a) || (n <= 1) || !(k %= n)) return;
    // 計算最優的k和n
    k = k < 0 ? k + n : k;
        
    // 開始真的移動了, 首先確定趟數, 一般趟就夠了,特殊的就是能夠整除的需要多趟
    
    for (i = 0; t<n && i < k; ++i) {
        //開始迴圈了, 結束條件式迴圈到頭了
        for (j = (i + k) % n; j != i; j = (j + k) % n) {
            ++t;
            int tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
        }
    }
}

關於上面

    // 前面兩個是沒有移動的必要, 後面是已經整除形成周期了也不需要移動
    if ((!a) || (n <= 1) || !(k %= n)) return;

就是C中最簡單的異常機制聽過事先 if判斷條件達到異常應對作用. 應該是最簡單的異常處理, 也是常用的一種方式.

附錄完整的測試demo.c

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

// 數組列印函數
void aprint(int a[], int n);
// 數組移動函數
void convert(int a[], int n, int k);

//添加一個數組列印巨集, a只能是數組
#define ALEN(a) \
    sizeof(a)/sizeof(*a)
#define APRINT(a) \
    aprint(a, ALEN(a))

/*
 *  面試問題 假如有數組 int a[] = {1,2,3,4,5,6,7}, n = 7表示長度, k = 2表示移動步長
 * 移動結果變成 1234567=> 6712345. 
 * 同樣假如 k=3移動三步 1234567=> 5671234
 * 實現一個移動函數
 * void convert(int a[], int n, int k);
 */
int main(int argc, char* argv[]) {
    // 實現主體邏輯
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    
    APRINT(a);
    convert(a, ALEN(a), 4);
    APRINT(a);
    convert(a, ALEN(a), -4);
    APRINT(a);

    return system("pause");
}

// 數組列印函數
void 
aprint(int a[], int n) {
    int i = -1;
    printf("now data: ");
    while (++i < n)
        printf("%d", a[i]);
    putchar('\n');
}

// 數組移動函數
void 
convert(int a[], int n, int k) {
    int t=1, i, j;// t是為了數據記錄終止條件的
    // 前面兩個是沒有移動的必要, 後面是已經整除形成周期了也不需要移動
    if ((!a) || (n <= 1) || !(k %= n)) return;
    // 計算最優的k和n
    k = k < 0 ? k + n : k;
        
    // 開始真的移動了, 首先確定趟數, 一般趟就夠了,特殊的就是能夠整除的需要多趟
    
    for (i = 0; t<n && i < k; ++i) {
        //開始迴圈了, 結束條件式迴圈到頭了
        for (j = (i + k) % n; j != i; j = (j + k) % n) {
            ++t;
            int tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
        }
    }
}
View Code

大家是不是覺得很簡單. 這就是異常的本質, 分支.

 

前言

  到這裡會再說另一個和if很像如下處理

最後加上default對於所有情況都處理好.也是異常處理的一種手段.

C開發中或者一個程式介面設計中一種很古老的一種設計有異常判斷介面方法通過return 方法處理.

假如我們 需要寫個函數返回兩個數相除的函數.

#define _RT_OK        (0)                //結果正確的返回巨集
#define _RT_EB        (-1)            //錯誤基類型,所有錯誤都可用它,在不清楚的情況下
#define _RT_EP        (-2)            //參數錯誤
#define _RT_EM        (-3)            //記憶體分配錯誤
#define _RT_EC        (-4)            //文件已經讀取完畢或表示鏈接關閉
#define _RT_EF        (-5)            //文件打開失敗

int 
div(double a, double b, double *pc){
     if(b==0 || !pa) return _RT_EP;
     *pc = a / b;
     return _RT_OK;    
}

上面這種思路是一種復古套路也許只能C介面封裝能夠看見. 特別實誠. 這個技巧值得擁有.

返回函數運行的狀態碼, 通過指針返回最終結果值. 再扯一點.上面介面設計有一個小瑕疵,調用的時候

需要大量的if 判斷. 假如是後端開發. 對於非法請求直接fake. (exit) 不用給前端返回狀態碼. 降低帶寬.

 好了到這裡基本上, 簡單開發中異常處理方式簡單都介紹完了. 後面會實現 try ... catch機制. 

 

正文

  到這裡我們先看C中異常處理的魔法函數. 一個比goto更跳躍的函數. 支持函數之間跳躍.首先看一種實現的函數聲明

// Function prototypes
int __cdecl setjmp(
    _Out_ jmp_buf _Buf
    );
    __declspec(noreturn) void __cdecl longjmp(
        _In_ jmp_buf _Buf,
        _In_ int     _Value
        );

上面看不明白的關鍵字(VS上的)直接忽略, 第一個函數setjmp 設置標誌.第一次使用返回0.後面到這裡來了 返回的是longjmp 第二個參數設置的值.

這裡有個未定義現象. 就是千萬不要用 longjmp 返回0 測試代碼

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

// 這裡測試基礎代碼 longjmp(jbf, 0)
int main(void) {
    volatile a = 0;
    jmp_buf jbf;
    int rt;

    rt = setjmp(jbf);
    if (rt == 0) {
        printf("a %d => %d\n", ++a, rt);
        if (a == 2)
            exit(0);
    }
    else {
        printf("b %d => %d\n", ++a, rt);
    }
    
    // 簡單跳躍一下
    if (a == 1)
        longjmp(jbf, 0);

    return system("pause");
}

運行結果就是未定義按照平臺而定了.看下麵

.

還有一個 jmp_buf 結構

    #define _JBLEN  16
    #define _JBTYPE int

    typedef struct __JUMP_BUFFER
    {
        unsigned long Ebp;
        unsigned long Ebx;
        unsigned long Edi;
        unsigned long Esi;
        unsigned long Esp;
        unsigned long Eip;
        unsigned long Registration;
        unsigned long TryLevel;
        unsigned long Cookie;
        unsigned long UnwindFunc;
        unsigned long UnwindData[6];
    } _JUMP_BUFFER;

主要是保存當前寄存器信息讓其longjmp的時候能夠跳轉. 這方面需要彙編知識. 本人不會. 有機會再學.

希望到這裡關於 C的基礎 setjmp longjmp api說清楚了. 下麵看一個完整的 處理異常的案例

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

// 帶異常機制的觸發運算
double jmpdiv(jmp_buf jbf, double a, double b);

// 這裡設置異常機制
int main(int argc, char* argv[]){
    jmp_buf jbf;
    double a = 2.0, b = 0, c;
    int i = 0;
    // 隨機數
    while(++i <= 10) {
        printf("%d => ", i);
        // 第一次調用setjmp 返回的值就為0
        switch(setjmp(jbf)){
        case 0:
            b = rand() % 5;
            c = jmpdiv(jbf, a, b);
            // try部分
            printf("%lf / %lf = %lf\n", a,b,c);
            break;
        case -1:
            // 相當於catch 部分
            fprintf(stderr, "throw new div is zero!\n");
            break;
        default:
            fprintf(stderr, "uncat error!");
        }
    }
    return 0;
}

double 
jmpdiv(jmp_buf jbf, double a, double b) {
    // 拋出異常
    if(b == 0) 
        longjmp(jbf, -1);
    return a/b;
}

不好意思,今天 說的有點水. 講的不好. 畢竟就是2個api. 用法也很固定. 主要C開發用在協程設計上. 異常處理基本if else switch goto都能解決了.

下麵列舉一個 關於 try ... catch 封裝成巨集的用法

#include <setjmp.h>

/* try 開始操作 */
#define TRYBEGIN(var) {\
    jmp_buf var;\
    switch (setjmp(var)){\
    case 0:

/* catch檢測錯誤值 */
#define CATCH(z) \
    break;\
case z:

/* 預設捕捉的錯誤信息. 推薦在CATCH最後,如果CATCH存在的話 */
#define CATCHBASE() \
    default:    

/* try最後結束標誌 */
#define TRYEND \
    break;\
    }\
}

/* throw 拋出異常 */
#define THROW(var, z) \
    longjmp(var, z)

簡單實用如下

    TRYBEGIN(jbf) {
        puts("第一次運行到這個");
    }
    CATCH(1) {
        
    }
    CATCHBASE() {
        
    }
    TRYEND

到這裡. 分享一個簡易的通過setjmp 和 longjmp 實現協程案例

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

// 函數棧之間跳轉用的變數
jmp_buf _mtask, _ctask;

// 聲明測試介面
void cushion(void);
void child(void);

int i = 0;

// 主邏輯 執行測試
int main(void) {
    if(!setjmp(_mtask))
        cushion();
    for(i=0;i<5;++i) {
        puts("Parent");
        if(!setjmp(_mtask))
            longjmp(_ctask, 1);
    }
}

// 聲明測試介面
void 
cushion(void) {
    char space[1024];
    space[1023] = 1;
    child();
}

void 
child(void) {
    for(i=0;i<5;++i) {
        puts("Child loop begin");
        // 第一次使用setjmp 函數返回0
        if(!setjmp(_ctask)) 
            longjmp(_mtask, 1);

        puts("Child loop end");
        if(!setjmp(_ctask))
            longjmp(_mtask, 1);
    }
}

我這裡讓其運行幾次之後就直接結束了. 沒上運行截圖了. 對於setjmp 坑還是很多的. 棧區不夠, 變數狀態. 推薦自己

用幾次感受一下. 不需要深究.只需要知道通過 setjmp + longjmp 能夠實現異常機制try catch就可以了. 再扯一點對於finally實現難

一點點. 大家可以結合上面協程. 思考一下也可以實現的.

到這裡 基本上就關於 異常機制的本質就結束了. 分支 + 跳轉  

 

後記

  錯誤是難免的, 交流修改.互相提高認識. setjmp.h 的深入可以看

setjmp的正確使用   http://blog.codingnow.com/2010/05/setjmp.html

以後有機會還是分享一些實際案例開發吧. 如何設計,如何性能優化.拜拜~~~

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ...
  • 2016-04-03 實現對二維數組排序❖ 對購物車商品表格實現:按數量,按單價分別降序/升序排序。❖ 查閱參考手中, usort( )函數的說明。 要求效果圖如下: 註:此處下方實際應有四個按鈕,分別控制四種不同的排序,因為一些特殊的原因無法給出。 實現代碼如下: sort-cart.php: 這 ...
  • 環境:windows 7 64位;python2.7;IDE pycharm2016.1 功能: 批量下載百度貼吧某吧某頁的所有帖子中的所有圖片 使用方法: 1.安裝python2.7,安裝re模塊,安裝urllib2模塊 2.複製以下源代碼保存為tbImgiDownloader.py文件 3.打開 ...
  • 一、刪除首碼 '*' 1 #include<iostream> 2 #include<cstdio> 3 4 using namespace std; 5 6 //主函數 7 int main() 8 { 9 char chr[20],*b,*p; //字元串緩衝區;字元串頭指針;字元串臨時指針 1 ...
  • 在使用Json傳值並且使用@RequestBody註解的時候需要註意一些問題: 第一條容易理解,因為RequestBody就是request的inputStream,這個流在第一次使用該註解後會關閉,後面的都會報錯(stream closed)。 第二條如果沒有包含前臺傳來的欄位,就會報錯:Unre ...
  • 簡介:本文主要講:函數的定義,外部參數的用處,無返回類型的三種函數定義方式 閉包的定義,閉包的概念和用法,尾隨閉包的寫法,解除迴圈引用的方法 一、函數: 代碼實現 函數的定義 格式 func 函數名(行參列表) -> 返回值 {代碼實現} 調用 let result = 函數名(值1, 參數2: 值 ...
  • c 和 c++ 最大的特點就是對記憶體的自由操作,數據類型,其實都是對記憶體的一種解釋方式。C語言中常用的一個技巧就是尾隨數據,網路編程中經常會用到這個特性, 特別是以前寫完成埠的時候,這個特性肯定是會用到,跟IOCP的API特性相關。c++中也有類似的new也可以使用。 e1:尾隨記憶體與指針解釋 輸 ...
  • 我們都知道,在應屆面試的時候,問到最多的就是快速排序,快速排序是最經典、最常用的排序演算法,因為它的平均效率最優,也最穩定。 快速排序使用了分治的演算法思想,分治演算法本身理解起來很符合人類的思路(遞歸是很容易被理解的),其最關鍵的一步,就是劃分了,演算法導論上介紹了一種劃分方法,和我在數據結構課上學的略有 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...