C異常處理實現: setjmp和longjmp

來源:互聯網
上載者:User

此文為internet上選摘,過後我會用自己的理解補充此文。

------------

將對setjmp與longjmp的具體使用方法和適用的場合,進行一個非常全面的闡述。

另外請特別注意,setjmp函數與longjmp函數總是組合起來使用,它們是緊密相關的一對操作,只有將它們結合起來使用,才能達到程式控制流程有效轉移的目的,才能按照程式員的預先設計的意圖,去實現對程式中可能出現的異常進行集中處理。

與goto語句的作用類似,它能實現本地的跳轉

這種情況容易理解,不過還是列舉出一個樣本程式吧!如下:

void main( void )
{
int jmpret;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
// 判斷程式遠行中,是否出現錯誤,如果有錯誤,則跳轉!
if(1) longjmp(mark, 1);

// 其它代碼的執行
// 判斷程式遠行中,是否出現錯誤,如果有錯誤,則跳轉!
if(2) longjmp(mark, 2);

// 其它代碼的執行
// 判斷程式遠行中,是否出現錯誤,如果有錯誤,則跳轉!
if(-1) longjmp(mark, -1);

// 其它代碼的執行
}
else
{
// 錯誤處理模組
switch (jmpret)
{
case 1:
printf( "Error 1"n");
break;
case 2:
printf( "Error 2"n");
break;
case 3:
printf( "Error 3"n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}

return;
}

   上面的常式非常地簡單,其中程式中使用到了異常處理的機制,這使得程式的代碼非常緊湊、清晰,易於理解。在程式運行過程中,當異常情況出現後,控制流程是 進行了一個本地跳轉(進入到異常處理的代碼模組,是在同一個函數的內部),這種情況其實也可以用goto語句來予以很好的實現,但是,顯然setjmp與 longjmp的方式,更為嚴謹一些,也更為友善。程式的執行流17-1所示。

setjmp與longjmp相結合,實現程式的非本地的跳轉

呵呵!這就是goto語句所不能實現的。也正因為如此,所以才說在C語言中,setjmp與longjmp相結合的方式,它提供了真正意義上的異常處 理機制。其實上一篇文章中的那個常式,已經示範了longjmp函數的非本地跳轉的情境。這裡為了更清晰示範本地跳轉與非本地跳轉,這兩者之間的區別,我 們在上面剛才的那個常式基礎上,進行很小的一點改動,代碼如下:

void Func1()
{
// 其它代碼的執行
// 判斷程式遠行中,是否出現錯誤,如果有錯誤,則跳轉!
if(1) longjmp(mark, 1);
}

void Func2()
{
// 其它代碼的執行
// 判斷程式遠行中,是否出現錯誤,如果有錯誤,則跳轉!
if(2) longjmp(mark, 2);
}

void Func3()
{
// 其它代碼的執行
// 判斷程式遠行中,是否出現錯誤,如果有錯誤,則跳轉!
if(-1) longjmp(mark, -1);
}

void main( void )
{
int jmpret;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行

// 下面的這些函數執行過程中,有可能出現異常
Func1();

Func2();

Func3();

// 其它代碼的執行
}
else
{
// 錯誤處理模組
switch (jmpret)
{
case 1:
printf( "Error 1"n");
break;
case 2:
printf( "Error 2"n");
break;
case 3:
printf( "Error 3"n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}

return;
}

回顧一下,這與C++中提供的異常處理模型是不是很相近。異常的傳遞是可以跨越一個或多個函數。這的確為C程式員提供了一種較完善的異常處理編程的機制或手段。

setjmp和longjmp使用時,需要特別注意的事情

1、setjmp與longjmp結合使用時,它們必須有嚴格的先後執行順序,也即先調用setjmp函數,之後再調用longjmp函數,以恢複到 先前被儲存的“程式執行點”。否則,如果在setjmp調用之前,執行longjmp函數,將導致程式的執行流變的不可預測,很容易導致程式崩潰而退出。 請看樣本程式,代碼如下:

class Test
{
public:
Test()
~Test()
}obj;

//注意,上面聲明了一個全域變數obj

void main( void )
{
int jmpret;

// 注意,這裡將會導致程式崩潰,無條件退出
Func1();
while(1);

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行

// 下面的這些函數執行過程中,有可能出現異常
Func1();

Func2();

Func3();

// 其它代碼的執行
}
else
{
// 錯誤處理模組
switch (jmpret)
{
case 1:
printf( "Error 1"n");
break;
case 2:
printf( "Error 2"n");
break;
case 3:
printf( "Error 3"n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}

return;
}

  上面的程式運行結果,如下:
構造對象
Press any key to continue

   的確,上面程式崩潰了,由於在Func1()函數內,調用了longjmp,但此時程式還沒有調用setjmp來儲存一個程式執行點。因此,程式的執行 流變的不可預測。這樣導致的程式後果是非常嚴重的,例如說,上面的程式中,有一個對象被構造了,但程式崩潰退出時,它的解構函式並沒有被系統來調用,得以 清除一些必要的資源。所以這樣的程式是非常危險的。(另外請注意,上面的程式是一個C++程式,所以大家示範並測試這個常式時,把源檔案的副檔名改為 xxx.cpp)。

  2、除了要求先調用setjmp函數,之後再調用longjmp函數(也即longjmp必須有對應的setjmp函數)之外。另外,還有一個很重要的規則,那就是longjmp的調用是有一定域範圍要求的。這未免太抽象了,還是先看一個樣本,如下:

int Sub_Func()
{
int jmpret, be_modify;

be_modify = 0;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
}
else
{
// 錯誤處理模組
switch (jmpret)
{
case 1:
printf( "Error 1"n");
break;
case 2:
printf( "Error 2"n");
break;
case 3:
printf( "Error 3"n");
break;
default :
printf( "Unknown Error");
break;
}

//注意這一語句,程式有條件地退出
if (be_modify==0) exit(0);
}

return jmpret;
}

void main( void )
{
Sub_Func();

// 注意,雖然longjmp的調用是在setjmp之後,但是它超出了setjmp的作用範圍。
longjmp(mark, 1);
}

  如果你運行或調試(單步跟蹤)一下上面程式,發現它真是挺神奇的,居然longjmp執行時,程式還能夠返回到setjmp的執行點,程式正常退出。但是這就說明了上面的這個常式的沒有問題嗎?我們對這個程式小改一下,如下:

int Sub_Func()
{
// 注意,這裡改動了一點
int be_modify, jmpret;

be_modify = 0;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
}
else
{
// 錯誤處理模組
switch (jmpret)
{
case 1:
printf( "Error 1"n");
break;
case 2:
printf( "Error 2"n");
break;
case 3:
printf( "Error 3"n");
break;
default :
printf( "Unknown Error");
break;
}

//注意這一語句,程式有條件地退出
if (be_modify==0) exit(0);
}

return jmpret;
}

void main( void )
{
Sub_Func();

// 注意,雖然longjmp的調用是在setjmp之後,但是它超出了setjmp的作用範圍。
longjmp(mark, 1);
}

   運行或調試(單步跟蹤)上面的程式,發現它崩潰了,為什嗎?這就是因為,“在調用setjmp的函數返回之前,調用longjmp,否則結果不可預料” (這在上一篇文章中已經提到過,MSDN中做了特別的說明)。為什麼這樣做會導致不可預料?其實仔細想想,原因也很簡單,那就是因為,當setjmp函數 調用時,它儲存的程式執行點環境,只應該在當前的函數範圍以內(或以後)才會有效。如果函數返回到了上層(或更上層)的函數環境中,那麼setjmp保 存的程式的環境也將會無效,因為堆棧中的資料此時將可能發生覆蓋,所以當然會導致不可預料的執行後果。

   3、不要假象寄存器類型的變數將總會保持不變。在調用longjmp之後,通過setjmp所返回的控制流程中,常式中寄存器類型的變數將不會被恢複。 (MSDN中做了特別的說明,上一篇文章中,這也已經提到過)。寄存器類型的變數,是指為了提高程式的運行效率,變數不被儲存在記憶體中,而是直接被儲存在 寄存器中。寄存器類型的變數一般都是臨時變數,在C語言中,通過register定義,或直接嵌入彙編代碼的程式。這種類型的變數一般很少採用,所以在使 用setjmp和longjmp時,基本上不用考慮到這一點。

4、MSDN中還做了特別的說明,“在C+ +程式中,小心對setjmp和longjmp的使用,因為setjmp和longjmp並不能很好地支援C++中物件導向的語義。因此在C++程式中, 使用C++提供的異常處理機制將會更加安全。”雖然說C++能非常好的相容C,但是這並非是100%的完全相容。例如,這裡就是一個很好的例子,在C++ 程式中,它不能很好地與setjmp和longjmp和平共處。在後面的一些文章中,有關專門討論C++如何相容支援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.