C/C++編程筆記:C 語言中 setjmp 和 longjmp,教你正確函數(shù)內(nèi)跳轉(zhuǎn)!
在 C 語言中,我們不能使用goto語句來跳轉(zhuǎn)到另一個函數(shù)中的某個label處;但提供了兩個函數(shù)——setjmp和longjmp來完成這種類型的分支跳轉(zhuǎn)。后面我們會看到這兩個函數(shù)在處理異常上面的非常有用。

setjmp 和 longjmp 使用方法
我們都知道要想在一個函數(shù)內(nèi)進行跳轉(zhuǎn),可以使用goto語句(不知怎么該語句在中國學(xué)生眼中就是臭名昭著,幾乎所有國內(nèi)教材都一刀切地教大家盡量不要使用它,但在我看來,這根本不是語言的問題,而是使用該語言的人,看看 Linux 內(nèi)核中遍地是 goto 語句的應(yīng)用吧?。?,但如果從一個函數(shù)內(nèi)跳轉(zhuǎn)到另一個函數(shù)的某處,goto 是不能完成的,那該如何實現(xiàn)呢?
函數(shù)間跳轉(zhuǎn)原理
我們要實現(xiàn)的一個GOTO語句(我自己定義的),能實現(xiàn)在函數(shù)間進行任意跳轉(zhuǎn),如下例,在函數(shù) g() 中有條語句GOTO Label;可以跳轉(zhuǎn)到 f() 函數(shù)的Label:標(biāo)簽所指向的位置,那么我們該如何實現(xiàn)呢?

首先我們要知道,實現(xiàn)這種類型的跳轉(zhuǎn),和操作系統(tǒng)中任務(wù)切換的上下文切換有點類似,我們只需要恢復(fù) Label 標(biāo)簽處函數(shù)上下文即可。函數(shù)的上下文包括以下內(nèi)容:
(1)函數(shù)棧幀,主要是棧幀指針BP和棧頂指針SP
(2)程序指針PC,此處為指向 Label 語句的地址
(3)其它寄存器,這是和體系相關(guān)的,在 x86 體系下需要保存有的 AX/BX/CX 等等 callee-regs。
這樣,在執(zhí)行GOTO Label;這條語句,我們恢復(fù) Label 處的上下文,即完成跳轉(zhuǎn)到 Label 處的功能。
如果你讀過 Linux 操作系統(tǒng)進程切換的源碼,你會很明白 Linux 會把進程的上下文保存在 task_struct
結(jié)構(gòu)體中,切換時直接恢復(fù)。這里我們也可以這樣做,將 Label 處的函數(shù)上下文保存在某個結(jié)構(gòu)體中,但執(zhí)行到 GOTO Label
語句時,我們從該結(jié)構(gòu)體中恢復(fù)函數(shù)的上下文。
這就是函數(shù)間進行跳轉(zhuǎn)的基本原理,而 C 語言中 setjmp 和 longjmp 就為我們完成了這樣的保存上下文和切換上下文的工作。
函數(shù)原型

setjmp 函數(shù)的功能是將函數(shù)在此處的上下文保存在 jmp_buf 結(jié)構(gòu)體中,以供 longjmp 從此結(jié)構(gòu)體中恢復(fù)。
(1)參數(shù) env 即為保存上下文的 jmp_buf 結(jié)構(gòu)體變量;
(2)如果直接調(diào)用該函數(shù),返回值為 0; 若該函數(shù)從 longjmp 調(diào)用返回,返回值為非零,由 longjmp 函數(shù)提供。根據(jù)函數(shù)的返回值,我們就可以知道 setjmp 函數(shù)調(diào)用是第一次直接調(diào)用,還是由其它地方跳轉(zhuǎn)過來的。

longjmp 函數(shù)的功能是從 jmp_buf 結(jié)構(gòu)體中恢復(fù)由 setjmp 函數(shù)保存的上下文,該函數(shù)不返回,而是從 setjmp 函數(shù)中返回。
(1)參數(shù) env 是由 setjmp 函數(shù)保存過的上下文。
(2)參數(shù) val 表示從 longjmp 函數(shù)傳遞給 setjmp 函數(shù)的返回值,如果 val 值為0, setjmp 將會返回1,否則返回 val。
(3)longjmp 不直接返回,而是從 setjmp 函數(shù)中返回,longjmp 執(zhí)行完之后,程序就像剛從 setjmp 函數(shù)返回一樣。
簡單實例
下面是個簡單的例子,雖然還只是函數(shù)內(nèi)跳轉(zhuǎn),但足以說明這兩個函數(shù)的功能了。

運行該程序得到的結(jié)果為:

C 語言異常處理
Java、C# 等面向?qū)ο笳Z言中都有異常處理的機制,如下就是典型的 Java 中異常處理的代碼,兩個數(shù)相除,如果被除數(shù)為0拋出異常,在函數(shù) f() 中可以獲取該異常并進行處理:

在 C 語言中雖然沒有類似的異常處理機制,但是我們可以使用 setjmp 和 longjmp 來模擬實現(xiàn)該功能,這也是這兩個函數(shù)的一個重要的應(yīng)用:

如果復(fù)雜一點,可以根據(jù) longjmp 傳遞的返回值來判斷各種不同的異常,來進行區(qū)別的處理,代碼結(jié)構(gòu)如下:

希望對大家有幫助~
學(xué)習(xí)C/C++可以關(guān)注UP!
想要學(xué)習(xí)C/C++基礎(chǔ)知識和項目知識,可以關(guān)注UP上傳的視頻,希望對你有幫助,會持續(xù)更新哦~