新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 牛人業(yè)話 > C語言的那些小秘密之異常處理

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

作者: 時(shí)間:2015-07-07 來源:網(wǎng)絡(luò) 收藏

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

本文引用地址:http://butianyuan.cn/article/276910.htm

  首先我們來了解下異常處理,異常是一個(gè)在程序執(zhí)行期間發(fā)生的事件,它中斷正在執(zhí)行的程序的正常的指令流,而我們的異常處理功能提供了處理程序運(yùn)行時(shí)出現(xiàn)的任何意外或異常情況的方法。

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

  setjmp()函數(shù)原型:

  int ( jmp_buf env );

  如果我們打開源代碼會(huì)發(fā)現(xiàn)在setjmp()函數(shù)中涉及到很多的寄存器的操作,如Ebp、Ebx、Edi、Esi、Esp、 Eip等等,在此就不一一例舉了,我們無非是想向讀者說明一個(gè)問題,那就是在調(diào)用setjmp()函數(shù)的過程中保存程序的當(dāng)前運(yùn)行時(shí)的堆棧環(huán)境,保存這些堆棧環(huán)境有什么用呢?接下來我們看看longjmp()函數(shù)。

  longjmp()函數(shù)原型:

  void longjmp( jmp_buf env, int value );

  剛剛上面的函數(shù)功能是保存程序執(zhí)行時(shí)候的堆棧環(huán)境,我們發(fā)現(xiàn)在longjmp()函數(shù)里也有一個(gè)jmp_buf類型的env變量,這其實(shí)是為了保證接下來調(diào)用longjmp時(shí),會(huì)根據(jù)這個(gè)曾經(jīng)保存的變量來恢復(fù)先前的環(huán)境,并且當(dāng)前的程序控制流,會(huì)因此而返回到最初調(diào)用setjmp()函數(shù)時(shí)的程序執(zhí)行點(diǎn)。此時(shí),在接下來的控制流的例程中,所能訪問的所有的變量,包含了longjmp函數(shù)調(diào)用時(shí)所擁有的變量。我們就這樣說讀者可能就得有點(diǎn)抽象了,那我們還是來看看一段代碼后再來分析吧,在此特地給出了一個(gè)簡(jiǎn)單的代碼,由易到難的來分析。

  [cpp] view plaincopy#include

  #include

  jmp_buf buf;

  void error_code(void)

  {

  longjmp(buf,1);

  }

  int main()

  {

  double a,b;

  printf("請(qǐng)輸入被除數(shù):");

  scanf("%lf",&a);

  printf("請(qǐng)輸入除數(shù):");

  if(setjmp(buf)==0)

  {

  scanf("%lf",&b);

  if(0==b)

  error_code();

  printf("相除的結(jié)果為:%fn",a/b);

  }

  else

  printf("出現(xiàn)錯(cuò)誤除數(shù)為0n");

  return 0;

  }

  運(yùn)行結(jié)果為:

  

 

  [cpp] view plaincopy請(qǐng)輸入被除數(shù):12

  請(qǐng)輸入除數(shù):0

  出現(xiàn)錯(cuò)誤除數(shù)為0

  Press any key to continue

  看了上面的運(yùn)行結(jié)果,現(xiàn)在我們接著上面的講,在一開始的部分我們并沒有具體的交代setjmp()函數(shù)和longjmp()函數(shù)的返回值和參數(shù)的具體含義。兩個(gè)函數(shù)中的env變量保存的是調(diào)用setjmp()函數(shù)的時(shí)候當(dāng)前運(yùn)行程序的堆棧信息,而longjmp()函數(shù)的調(diào)用就是根據(jù)在調(diào)用setjmp()函數(shù)的時(shí)候的堆棧信息返回到最初調(diào)用setjmp()函數(shù)的地方,而其中的第二個(gè)參數(shù)就是此刻setjmp()函數(shù)的返回值,但是值得注意的就是調(diào)用longjmp()函數(shù)之后setjmp函數(shù)返回的值必須是非零值,如果longjmp傳送的value參數(shù)值為0,那么實(shí)際上setjmp返回的值是1。一開始我們調(diào)用setjmp()函數(shù)的時(shí)候,它的返回值為0,之后再調(diào)用longjmp()函數(shù)的時(shí)候,通過設(shè)定longjmp()函數(shù)的第二個(gè)參數(shù)來設(shè)定它的返回值。

  現(xiàn)在我們來分析上邊的代碼,在main()函數(shù)中,我們最初調(diào)用setjmp()函數(shù)的時(shí)候,把當(dāng)前的環(huán)境信息保存在了buf中,函數(shù)返回0,然后往下運(yùn)行,我們輸入0。通過if語句發(fā)現(xiàn)b的值為0那么就調(diào)用error_code()函數(shù)來進(jìn)行處理,在該函數(shù)中我們使用了longjmp()函數(shù),其使用方式為longjmp(buf,1);,通過上面的講解,我們知道第一個(gè)參數(shù)的作用是用來得到最初調(diào)用setjmp()函數(shù)是的環(huán)境信息,以便在使用longjmp()函數(shù)的時(shí)候能夠正確的返回到setjmp()函數(shù)最初的調(diào)用處,而后面的參數(shù)表示的返回到setjmp()函數(shù)的時(shí)候的返回值。我們?cè)诖朔祷?,所以執(zhí)行else部分的語句。

  分析完了上面的代碼,讀者應(yīng)該都知道了兩個(gè)函數(shù)的使用方法,值得注意的地方就是我們?cè)趕etjmp與longjmp結(jié)合使用時(shí),它們必須有嚴(yán)格的先后執(zhí)行順序,先調(diào)用setjmp函數(shù),之后再調(diào)用longjmp函數(shù),以恢復(fù)到先前被保存的“程序執(zhí)行點(diǎn)”。否則,假如在setjmp調(diào)用之前,執(zhí)行l(wèi)ongjmp函數(shù),將導(dǎo)致程序的執(zhí)行流變的不可猜測(cè),很輕易導(dǎo)致程序崩潰而退出。為了加深讀者的對(duì)于兩個(gè)函數(shù)參數(shù)的使用,我們看看下面的代碼:

  [cpp] view plaincopy#include

  #include

  #include

  #include

  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:%dn",str,value);

  if(1==value)

  {

  func2();

  }

  if(2==value)

  {

  func3();

  }

  return 0;

  }

  運(yùn)行結(jié)果為:

  

 

  [cpp] view plaincopyfunc1 return value:1

  func2 return value:2

  func3 return value:3

  Press any key to continue

  看看運(yùn)行結(jié)果,我們分析下代碼,在每個(gè)函數(shù)中我們調(diào)用longjmp()函數(shù),通過設(shè)置第二個(gè)參數(shù)為不同的值來改變setjmp()函數(shù)的返回值,然后我們通過判斷value值來打印出是那個(gè)函數(shù)的返回值,我們?cè)诖死e這個(gè)簡(jiǎn)單的代碼是要大家加深對(duì)于這兩個(gè)函數(shù)的參數(shù)的使用情況。如果我們?cè)谏厦娴拇a中稍作修改,在setjmp()函數(shù)的調(diào)用之前調(diào)用longjmp()函數(shù),我們發(fā)現(xiàn)此時(shí)沒有任何的輸出,程序直接崩潰掉退出了。

  接下來我們來看看一個(gè)函數(shù)的使用,如果對(duì)于這個(gè)函數(shù)不理解的讀者,可以多看幾次我給出的模擬該函數(shù)的實(shí)現(xiàn)代碼。

  頭文件: #include

  功能:設(shè)置某一信號(hào)的對(duì)應(yīng)動(dòng)作

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

  注意:第一個(gè)參數(shù)signum指明了所要處理的信號(hào)類型,它可以取除了SIGKILL和SIGSTOP外的任何一種信號(hào)。

  如果讀者是第一場(chǎng)接觸上面的函數(shù)的話可能有些不知道該如何著手,一時(shí)間有些難以理解,不知道到底是什么意思。別急,我們現(xiàn)在來逐一分析它到底是什么意思,我們?cè)谥v解之前再來看看它的另外一種表示方法。

  typedef void(*sig_t) ( int );

  sig_t signal(int signum,sig_t handler);

  把上面的函數(shù)原型拆分為了如上兩行代碼,現(xiàn)在我們分析下上面的兩行代碼。

  第一行代碼定義了一個(gè)函數(shù)指針(注:如果有對(duì)函數(shù)指針知識(shí)點(diǎn)不熟悉的讀者可以去閱讀我之前寫的那篇文章《C語言的那些小秘密之函數(shù)指針》),其類型為含有一個(gè)int型參數(shù),無返回值;

  第二行代碼中,signal函數(shù)的返回值是一個(gè)函數(shù)指針,與第一行我們定義的類型相同,第二個(gè)參數(shù)也為一個(gè)函數(shù)指針,其實(shí)signal的返回值就是第二個(gè)函數(shù)指針指向的函數(shù)地址。這樣說可能有不少讀者都有些懵的感覺,還是老方法,代碼最有說服力,我們還是為讀者模擬下signal的實(shí)現(xiàn)方式,呈現(xiàn)出一段代碼來分析下。

  [cpp] view plaincopy#include

  #include

  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;

  }

  運(yùn)行結(jié)果為:

  

 

  [cpp] view plaincopyhello world!!!

  Press any key to continue

  現(xiàn)在我們來分析下上面的代碼,我們采用上面的定義形式實(shí)現(xiàn)了如下兩行代碼:

  typedef void (*pfun) ();

  pfun signal_call(int a,pfun fdsa);

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

  [cpp] view plaincopy#include

  #include

  #include

  #include

  #include

  #include

  jmp_buf buf;

  int err;

  void handler( int num )

  {

  err = num;

  printf( "發(fā)生浮點(diǎn)計(jì)算異常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("請(qǐng)輸入被除數(shù):");

  scanf("%lf",&a);

  printf("請(qǐng)輸入除數(shù):");

  scanf("%lf",&b);

  printf( "a / b = %4.3gn", a/b);

  printf("發(fā)生異常時(shí)候不會(huì)被執(zhí)行的語句n");

  }

  return 0;

  }

  沒有發(fā)生異常時(shí)候的運(yùn)行結(jié)果:

  

 

  [cpp] view plaincopy請(qǐng)輸入被除數(shù):123

  請(qǐng)輸入除數(shù):3

  a / b = 41

  發(fā)生異常時(shí)候不會(huì)被執(zhí)行的語句

  Press any key to continue

  發(fā)生異常時(shí)候的運(yùn)行結(jié)果:

  

 

  [cpp] view plaincopy請(qǐng)輸入被除數(shù):12

  請(qǐng)輸入除數(shù):0

  發(fā)生浮點(diǎn)計(jì)算異常

  Press any key to continue

  現(xiàn)在來分析下上面的運(yùn)行結(jié)果,先看看_control87( 0, _MCW_EM );這句,可能很多讀者對(duì)于這代碼比較陌生,它的功能是開啟所有的浮點(diǎn)計(jì)算異常,通常情況下浮點(diǎn)計(jì)算異常是被屏蔽掉的,我們?yōu)榱四軌蚴沟媒酉聛淼膕ignal能夠捕捉到浮點(diǎn)計(jì)算異常,所以要將其開啟。在往下看我們通過signal( SIGFPE, handler )來綁定了一個(gè)浮點(diǎn)計(jì)算異常處理函數(shù),如果發(fā)生異常時(shí),那么就調(diào)用handler()函數(shù)來處理。接下來通過ret = setjmp( buf );保存程序運(yùn)行的環(huán)境信息,以便接下來的調(diào)用longjmp()函數(shù)能夠根據(jù)這個(gè)保存的信息返回該程序先前setjmp()函數(shù)的執(zhí)行點(diǎn)。同時(shí)我們對(duì)比兩次運(yùn)行的結(jié)果發(fā)現(xiàn)如果發(fā)現(xiàn)異常的時(shí)候接下來的打印語句“printf("發(fā)生異常時(shí)候不會(huì)被執(zhí)行的語句n");”是不會(huì)被執(zhí)行的,直接跳轉(zhuǎn)到我們綁定的handler()函數(shù)執(zhí)行了,當(dāng)然我們?cè)诖藘H僅是例舉一些簡(jiǎn)單的代碼教會(huì)讀者學(xué)會(huì)使用setjmp()函數(shù)和longjmp()函數(shù)來實(shí)現(xiàn)異常處理,讀者完全可以在此基礎(chǔ)上編寫出復(fù)雜的異常處理。

  到這兒C語言的異常處理部分就結(jié)束了,由于本人水平有限,博客中的不妥或錯(cuò)誤之處在所難免,殷切希望讀者批評(píng)指正。同時(shí)也歡迎讀者共同探討相關(guān)的內(nèi)容,如果樂意交流的話請(qǐng)留下你寶貴的意見。

c語言相關(guān)文章:c語言教程


c++相關(guān)文章:c++教程




關(guān)鍵詞: C語言 異常處理

評(píng)論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉