上一篇文章對setjmp函數(shù)與longjmp函數(shù)有了較全面的了解,尤其是這兩個函數(shù)的作用,函數(shù)所完成的功能,以及將setjmp函數(shù)與 longjmp函數(shù)組合起來,實現(xiàn)異常處理機制時,程序模塊控制流的執(zhí)行過程等。這里更深入一步,將對setjmp與longjmp的具體使用方法和適用的場合,進行一個非常全面的闡述。
另外請?zhí)貏e注意,setjmp函數(shù)與longjmp函數(shù)總是組合起來使用,它們是緊密相關的一對操作,只有將它們結合起來使用,才能達到程序控制流有效轉(zhuǎn)移的目的,才能按照程序員的預先設計的意圖,去實現(xiàn)對程序中可能出現(xiàn)的異常進行集中處理。
與goto語句的作用類似,它能實現(xiàn)本地的跳轉(zhuǎn)
這種情況容易理解,不過還是列舉出一個示例程序吧!如下:
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(1) longjmp(mark, 1);
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(2) longjmp(mark, 2);
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(-1) longjmp(mark, -1);
// 其它代碼的執(zhí)行
}
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;
}
上面的例程非常地簡單,其中程序中使用到了異常處理的機制,這使得程序的代碼非常緊湊、清晰,易于理解。在程序運行過程中,當異常情況出現(xiàn)后,控制流是進行了一個本地跳轉(zhuǎn)(進入到異常處理的代碼模塊,是在同一個函數(shù)的內(nèi)部),這種情況其實也可以用goto語句來予以很好的實現(xiàn),但是,顯然setjmp與 longjmp的方式,更為嚴謹一些,也更為友善。程序的執(zhí)行流如圖17-1所示。
setjmp與longjmp相結合,實現(xiàn)程序的非本地的跳轉(zhuǎn)
呵呵!這就是goto語句所不能實現(xiàn)的。也正因為如此,所以才說在C語言中,setjmp與longjmp相結合的方式,它提供了真正意義上的異常處理機制。其實上一篇文章中的那個例程,已經(jīng)演示了longjmp函數(shù)的非本地跳轉(zhuǎn)的場景。這里為了更清晰演示本地跳轉(zhuǎn)與非本地跳轉(zhuǎn),這兩者之間的區(qū)別,我們在上面剛才的那個例程基礎上,進行很小的一點改動,代碼如下:
void Func1()
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(1) longjmp(mark, 1);
}
void Func2()
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(2) longjmp(mark, 2);
}
void Func3()
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(-1) longjmp(mark, -1);
}
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執(zhí)行
// 下面的這些函數(shù)執(zhí)行過程中,有可能出現(xiàn)異常
Func1();
Func2();
Func3();
// 其它代碼的執(zhí)行
}
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++中提供的異常處理模型是不是很相近。異常的傳遞是可以跨越一個或多個函數(shù)。這的確為C程序員提供了一種較完善的異常處理編程的機制或手段。
setjmp和longjmp使用時,需要特別注意的事情
1、setjmp與longjmp結合使用時,它們必須有嚴格的先后執(zhí)行順序,也即先調(diào)用setjmp函數(shù),之后再調(diào)用longjmp函數(shù),以恢復到先前被保存的“程序執(zhí)行點”。否則,如果在setjmp調(diào)用之前,執(zhí)行l(wèi)ongjmp函數(shù),將導致程序的執(zhí)行流變的不可預測,很容易導致程序崩潰而退出。請看示例程序,代碼如下:
class Test
{
public:
Test() {printf("構造對象\n");}
~Test() {printf("析構對象\n");}
}obj;
//注意,上面聲明了一個全局變量obj
void main( void )
{
int jmpret;
// 注意,這里將會導致程序崩潰,無條件退出
Func1();
while(1);
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執(zhí)行
// 下面的這些函數(shù)執(zhí)行過程中,有可能出現(xiàn)異常
Func1();
Func2();
Func3();
// 其它代碼的執(zhí)行
}
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()函數(shù)內(nèi),調(diào)用了longjmp,但此時程序還沒有調(diào)用setjmp來保存一個程序執(zhí)行點。因此,程序的執(zhí)行流變的不可預測。這樣導致的程序后果是非常嚴重的,例如說,上面的程序中,有一個對象被構造了,但程序崩潰退出時,它的析構函數(shù)并沒有被系統(tǒng)來調(diào)用,得以清除一些必要的資源。所以這樣的程序是非常危險的。(另外請注意,上面的程序是一個C++程序,所以大家演示并測試這個例程時,把源文件的擴展名改為 xxx.cpp)。
另外請?zhí)貏e注意,setjmp函數(shù)與longjmp函數(shù)總是組合起來使用,它們是緊密相關的一對操作,只有將它們結合起來使用,才能達到程序控制流有效轉(zhuǎn)移的目的,才能按照程序員的預先設計的意圖,去實現(xiàn)對程序中可能出現(xiàn)的異常進行集中處理。
與goto語句的作用類似,它能實現(xiàn)本地的跳轉(zhuǎn)
這種情況容易理解,不過還是列舉出一個示例程序吧!如下:
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(1) longjmp(mark, 1);
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(2) longjmp(mark, 2);
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(-1) longjmp(mark, -1);
// 其它代碼的執(zhí)行
}
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;
}
上面的例程非常地簡單,其中程序中使用到了異常處理的機制,這使得程序的代碼非常緊湊、清晰,易于理解。在程序運行過程中,當異常情況出現(xiàn)后,控制流是進行了一個本地跳轉(zhuǎn)(進入到異常處理的代碼模塊,是在同一個函數(shù)的內(nèi)部),這種情況其實也可以用goto語句來予以很好的實現(xiàn),但是,顯然setjmp與 longjmp的方式,更為嚴謹一些,也更為友善。程序的執(zhí)行流如圖17-1所示。
setjmp與longjmp相結合,實現(xiàn)程序的非本地的跳轉(zhuǎn)
呵呵!這就是goto語句所不能實現(xiàn)的。也正因為如此,所以才說在C語言中,setjmp與longjmp相結合的方式,它提供了真正意義上的異常處理機制。其實上一篇文章中的那個例程,已經(jīng)演示了longjmp函數(shù)的非本地跳轉(zhuǎn)的場景。這里為了更清晰演示本地跳轉(zhuǎn)與非本地跳轉(zhuǎn),這兩者之間的區(qū)別,我們在上面剛才的那個例程基礎上,進行很小的一點改動,代碼如下:
void Func1()
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(1) longjmp(mark, 1);
}
void Func2()
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(2) longjmp(mark, 2);
}
void Func3()
{
// 其它代碼的執(zhí)行
// 判斷程序遠行中,是否出現(xiàn)錯誤,如果有錯誤,則跳轉(zhuǎn)!
if(-1) longjmp(mark, -1);
}
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執(zhí)行
// 下面的這些函數(shù)執(zhí)行過程中,有可能出現(xiàn)異常
Func1();
Func2();
Func3();
// 其它代碼的執(zhí)行
}
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++中提供的異常處理模型是不是很相近。異常的傳遞是可以跨越一個或多個函數(shù)。這的確為C程序員提供了一種較完善的異常處理編程的機制或手段。
setjmp和longjmp使用時,需要特別注意的事情
1、setjmp與longjmp結合使用時,它們必須有嚴格的先后執(zhí)行順序,也即先調(diào)用setjmp函數(shù),之后再調(diào)用longjmp函數(shù),以恢復到先前被保存的“程序執(zhí)行點”。否則,如果在setjmp調(diào)用之前,執(zhí)行l(wèi)ongjmp函數(shù),將導致程序的執(zhí)行流變的不可預測,很容易導致程序崩潰而退出。請看示例程序,代碼如下:
class Test
{
public:
Test() {printf("構造對象\n");}
~Test() {printf("析構對象\n");}
}obj;
//注意,上面聲明了一個全局變量obj
void main( void )
{
int jmpret;
// 注意,這里將會導致程序崩潰,無條件退出
Func1();
while(1);
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執(zhí)行
// 下面的這些函數(shù)執(zhí)行過程中,有可能出現(xiàn)異常
Func1();
Func2();
Func3();
// 其它代碼的執(zhí)行
}
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()函數(shù)內(nèi),調(diào)用了longjmp,但此時程序還沒有調(diào)用setjmp來保存一個程序執(zhí)行點。因此,程序的執(zhí)行流變的不可預測。這樣導致的程序后果是非常嚴重的,例如說,上面的程序中,有一個對象被構造了,但程序崩潰退出時,它的析構函數(shù)并沒有被系統(tǒng)來調(diào)用,得以清除一些必要的資源。所以這樣的程序是非常危險的。(另外請注意,上面的程序是一個C++程序,所以大家演示并測試這個例程時,把源文件的擴展名改為 xxx.cpp)。