C語言的那些小秘密之異常處理

來源:互聯網
上載者:User

很多讀者在此之前可能根本沒有使用或者聽說過C語言的異常處理,印象中都是C++或者java才有的東西,C語言怎麼會有異常處理呢?當然估計在大學出於一般的性的學習考試之類的話老師幾乎是不會提及C語言的異常處理的,那麼到底什麼是異常處理?C語言中又該如何來實現異常處理呢?那麼我們今天就講解一種典型的實現C語言異常處理的方法,以setjmp()函數和longjmp()函數實現的異常處理,我儘可能的把它們是怎樣實現異常處理方法講解清楚,希望接下來的內容對你有所協助,讓你學到一些新的東西。

首先我們來瞭解下異常處理,異常是一個在程式執行期間發生的事件,它中斷正在執行的程式的正常的指令流,而我們的異常處理功能提供了處理常式運行時出現的任何意外或異常情況的方法。

接下來我們先看看setjmp()函數和longjmp()函數實現C語言異常處理。

setjmp()函數原型:

int ( jmp_buf env );

如果我們開啟原始碼會發現在setjmp()函數中涉及到很多的寄存器的操作,如Ebp、Ebx、Edi、Esi、Esp、 Eip等等,在此就不一一例舉了,我們無非是想向讀者說明一個問題,那就是在調用setjmp()函數的過程中儲存程式的當前運行時的堆棧環境,儲存這些堆棧環境有什麼用呢?接下來我們看看longjmp()函數。

longjmp()函數原型:

void longjmp( jmp_buf env, int value );

剛剛上面的函數功能是儲存程式執行時候的堆棧環境,我們發現在longjmp()函數裡也有一個jmp_buf類型的env變數,這其實是為了保證接下來調用longjmp時,會根據這個曾經儲存的變數來恢複先前的環境,並且當前的程式控制流程,會因此而返回到最初調用setjmp()函數時的程式執行點。此時,在接下來的控制流程的常式中,所能訪問的所有的變數,包含了longjmp函數調用時所擁有的變數。我們就這樣說讀者可能就得有點抽象了,那我們還是來看看一段代碼後再來分析吧,在此特地給出了一個簡單的代碼,由易到難的來分析。

#include <stdio.h>#include <setjmp.h>jmp_buf buf;void error_code(void){   longjmp(buf,1);} int main(){double a,b;printf("請輸入被除數:");scanf("%lf",&a);printf("請輸入除數:");if(setjmp(buf)==0){scanf("%lf",&b);if(0==b)error_code();printf("相除的結果為:%f\n",a/b);}elseprintf("出現錯誤除數為0\n");return 0;}

運行結果為:

請輸入被除數:12請輸入除數:0出現錯誤除數為0Press any key to continue

看了上面的運行結果,現在我們接著上面的講,在一開始的部分我們並沒有具體的交代setjmp()函數和longjmp()函數的傳回值和參數的具體含義。兩個函數中的env變數儲存的是調用setjmp()函數的時候當前運行程式的堆棧資訊,而longjmp()函數的調用就是根據在調用setjmp()函數的時候的堆棧資訊返回到最初調用setjmp()函數的地方,而其中的第二個參數就是此刻setjmp()函數的傳回值,但是值得注意的就是調用longjmp()函數之後setjmp函數返回的值必須是非零值,如果longjmp傳送的value參數值為0,那麼實際上setjmp返回的值是1。一開始我們調用setjmp()函數的時候,它的傳回值為0,之後再調用longjmp()函數的時候,通過設定longjmp()函數的第二個參數來設定它的傳回值。

現在我們來分析上邊的代碼,在main()函數中,我們最初調用setjmp()函數的時候,把當前的環境資訊儲存在了buf中,函數返回0,然後往下運行,我們輸入0。通過if語句發現b的值為0那麼就調用error_code()函數來進行處理,在該函數中我們使用了longjmp()函數,其使用方式為longjmp(buf,1);,通過上面的講解,我們知道第一個參數的作用是用來得到最初調用setjmp()函數是的環境資訊,以便在使用longjmp()函數的時候能夠正確的返回到setjmp()函數最初的調用處,而後面的參數表示的返回到setjmp()函數的時候的傳回值。我們在此返回1,所以執行else部分的語句。

分析完了上面的代碼,讀者應該都知道了兩個函數的使用方法,值得注意的地方就是我們在setjmp與longjmp結合使用時,它們必須有嚴格的先後執行順序,先調用setjmp函數,之後再調用longjmp函數,以恢複到先前被儲存的“程式執行點”。否則,假如在setjmp調用之前,執行longjmp函數,將導致程式的執行流變的不可猜測,很輕易導致程式崩潰而退出。為了加深讀者的對於兩個函數參數的使用,我們看看下面的代碼:

#include <stdio.h>#include <setjmp.h>#include <stdlib.h>#include <string.h>jmp_buf buf;                   void func1(){longjmp(buf,1);}void func2(){longjmp(buf,2);}void func3(){longjmp(buf,3);}int main( void ){   int value;   char str[50];   value = setjmp( buf );   if( value == 0 )   {func1();    }switch( value ){case 1:strcpy( str, "func1 return value" );break;case 2:strcpy( str, "func2 return value" );break;case 3:strcpy( str, "func3 return value" );break;default:strcpy( str, "Other error value" );break;}printf("%s:%d\n",str,value);if(1==value){func2();}if(2==value){func3();}return 0;}

運行結果為:

func1 return value:1func2 return value:2func3 return value:3Press any key to continue

看看運行結果,我們分析下代碼,在每個函數中我們調用longjmp()函數,通過設定第二個參數為不同的值來改變setjmp()函數的傳回值,然後我們通過判斷value值來列印出是那個函數的傳回值,我們在此例舉這個簡單的代碼是要大家加深對於這兩個函數的參數的使用方式。如果我們在上面的代碼中稍作修改,在setjmp()函數的調用之前調用longjmp()函數,我們發現此時沒有任何的輸出,程式直接崩潰掉退出了。

接下來我們來看看一個函數的使用,如果對於這個函數不理解的讀者,可以多看幾次我給出的類比該函數的實現代碼。

標頭檔: #include<signal.h>

功能:設定某一訊號的對應動作

函數原型:void (*signal(int signum,void(* handler)(int)))(int);

注意:第一個參數signum指明了所要處理的訊號類型,它可以取除了SIGKILL和SIGSTOP外的任何一種訊號。

如果讀者是第一場接觸上面的函數的話可能有些不知道該如何著手,一時間有些難以理解,不知道到底是什麼意思。別急,我們現在來逐一分析它到底是什麼意思,我們在講解之前再來看看它的另外一種表示方法。

typedef void(*sig_t) ( int );

sig_t signal(int signum,sig_t handler);

把上面的函數原型拆分為了如上兩行代碼,現在我們分析下上面的兩行代碼。

第一行代碼定義了一個函數指標(註:如果有對函數指標知識點不熟悉的讀者可以去閱讀我之前寫的那篇文章《C語言的那些小秘密之函數指標》),其類型為含有一個int型參數,無傳回值;

第二行代碼中,signal函數的傳回值是一個函數指標,與第一行我們定義的類型相同,第二個參數也為一個函數指標,其實signal的傳回值就是第二個函數指標指向的函數地址。這樣說可能有不少讀者都有些懵的感覺,還是老方法,代碼最有說服力,我們還是為讀者類比下signal的實現方式,呈現出一段代碼來分析下。

#include <stdio.h>#include <stdlib.h>typedef void (*pfun) ();pfun signal_call(int a,pfun fdsa);pfun signal_call(int a,pfun fdsa){return fdsa;}void func(){printf("hello world!!!\n");}int main(){pfun p = func; signal_call(1,p)();return 0;}

運行結果為:

hello world!!!Press any key to continue

現在我們來分析下上面的代碼,我們採用上面的定義形式實現了如下兩行代碼:

typedef void (*pfun) ();

pfun signal_call(int a,pfun fdsa);

在接下來的main()函數中我們定義了一個函數指標p,使其指向了 func()函數,接下來我們使用了一句 signal_call(1,p)();代碼,實現了func函數調用,那麼這到底是怎麼實現的呢?那麼我們來分析下,前面的signal_call(1,p)返回的是一個函數指標,在代碼中我們發現其實返回的就是p,所以signal_call(1,p)();就可以變形為p(),看到這種形式我們這就可以很清楚的看出,它調用的就是我們代碼中的func()函數了。現在讀者明白了signal()函數的實現方法,接下來我們來看看一段使用signal捕捉除數為0時候的異常代碼。

#include <stdio.h>  #include <signal.h>   #include <setjmp.h>   #include <stdlib.h>   #include <float.h>   #include <string.h>jmp_buf buf;             int     err;           void handler( int num )  {  err = num;  printf( "發生浮點計算異常\n");  longjmp( buf, 1);  }int main( void )  {  double a, b;      char str[20];  int ret;_control87( 0, _MCW_EM );if( signal( SIGFPE, handler ) == SIG_ERR ){  printf("綁定失敗\n" );  abort();     }ret = setjmp( buf );  if(0 == ret )  {  printf("請輸入被除數:");  scanf("%lf",&a);  printf("請輸入除數:");  scanf("%lf",&b);  printf( "a / b = %4.3g\n", a/b);  printf("發生異常時候不會被執行的語句\n");  }return 0;  }  

沒有發生異常時候的運行結果:

請輸入被除數:123請輸入除數:3a / b =   41發生異常時候不會被執行的語句Press any key to continue

發生異常時候的運行結果:

請輸入被除數:12請輸入除數:0發生浮點計算異常Press any key to continue

現在來分析下上面的運行結果,先看看_control87( 0, _MCW_EM );這句,可能很多讀者對於這代碼比較陌生,它的功能是開啟所有的浮點計算異常,通常情況下浮點計算異常是被屏蔽掉的,我們為了能夠使得接下來的signal能夠捕捉到浮點計算異常,所以要將其開啟。在往下看我們通過signal( SIGFPE, handler )來綁定了一個浮點計算異常處理函數,如果發生異常時,那麼就調用handler()函數來處理。接下來通過ret = setjmp( buf );儲存程式啟動並執行環境資訊,以便接下來的調用longjmp()函數能夠根據這個儲存的資訊返回該程式先前setjmp()函數的執行點。同時我們對比兩次啟動並執行結果發現如果發現異常的時候接下來的列印語句“printf("發生異常時候不會被執行的語句\n");”是不會被執行的,直接跳轉到我們綁定的handler()函數執行了,當然我們在此僅僅是例舉一些簡單的代碼教會讀者學會使用setjmp()函數和longjmp()函數來實現異常處理,讀者完全可以在此基礎上編寫出複雜的異常處理。

到這兒C語言的異常處理部分就結束了,由於本人水平有限,部落格中的不妥或錯誤之處在所難免,殷切希望讀者批評指正。同時也歡迎讀者共同探討相關的內容,如果樂意交流的話請留下你寶貴的意見。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.