真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程-創(chuàng)新互聯(lián)

這篇文章主要介紹了怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

專注于為中小企業(yè)提供網(wǎng)站制作、成都做網(wǎng)站服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)燈塔免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了成百上千企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

一、前言

在 C 標(biāo)準(zhǔn)庫(kù)中,有兩個(gè)威力很猛的函數(shù):setjmp 和  longjmp,不知道各位小伙伴在代碼中是否使用過(guò)?我問(wèn)了身體的幾位同事,一部分人不認(rèn)識(shí)這兩個(gè)函數(shù),有一部分人知道這個(gè)函數(shù),但從來(lái)沒(méi)有使用過(guò)。

從知識(shí)點(diǎn)范圍來(lái)看,這兩個(gè)函數(shù)的功能比較單純,一個(gè)簡(jiǎn)單的示例代碼就能說(shuō)清楚了。但是,我們需要從這個(gè)知識(shí)點(diǎn)進(jìn)行發(fā)散、思考,在不同的維度上,把這個(gè)知識(shí)點(diǎn)與這個(gè)編程語(yǔ)言中其它類似的知識(shí)進(jìn)行聯(lián)想、對(duì)比;與其他編程語(yǔ)言中類似的概念進(jìn)行比較;然后再思考這個(gè)知識(shí)點(diǎn)可以使用在哪些場(chǎng)合,別人是怎么來(lái)使用它的。

例如:我們會(huì)把 setjmp/longjmp 與 goto 語(yǔ)句進(jìn)行功能上的比較;與 fork 函數(shù)從返回值上進(jìn)行類比;與 Python/Lua  語(yǔ)言中的協(xié)程進(jìn)行使用場(chǎng)景上的比較。

二、函數(shù)語(yǔ)法介紹

1. 最簡(jiǎn)示例

先不講道理,直接看一下這個(gè)最簡(jiǎn)單的示例代碼,看不懂也沒(méi)關(guān)系,混個(gè)臉熟:

int main() {     // 一個(gè)緩沖區(qū),用來(lái)暫存環(huán)境變量     jmp_buf buf;     printf("line1 \n");          // 保存此刻的上下文信息     int ret = setjmp(buf);     printf("ret = %d \n", ret);          // 檢查返回值類型     if (0 == ret)     {         // 返回值0:說(shuō)明是正常的函數(shù)調(diào)用返回         printf("line2 \n");                  // 主動(dòng)跳轉(zhuǎn)到 setjmp 那條語(yǔ)句處         longjmp(buf, 1);     }     else     {         // 返回值非0:說(shuō)明是從遠(yuǎn)程跳轉(zhuǎn)過(guò)來(lái)的         printf("line3 \n");     }     printf("line4 \n");     return 0; }

執(zhí)行結(jié)果:

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程

執(zhí)行順序如下(如果不明白就不要深究,看完下面的解釋再回過(guò)頭來(lái)看):

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程

2. 函數(shù)說(shuō)明

首先來(lái)看下這個(gè) 2 個(gè)函數(shù)的簽名:

int setjmp(jmp_buf env); void longjmp(jmp_buf env, int value);

它們都在頭文件 setjmp.h 中進(jìn)行聲明,維基百科的解釋如下:

  • setjmp: Sets up the local jmp_buf buffer and initializes it for the jump.  This routine saves the program's calling environment in the environment buffer  specified by the env argument for later use by longjmp. If the return is from a  direct invocation, setjmp returns 0. If the return is from a call to longjmp,  setjmp returns a nonzero value。

  • longjmp:Restores the context of the environment buffer env that was saved by  invocation of the setjmp routine in the same invocation of the program. Invoking  longjmp from a nested signal handler is undefined. The value specified by value  is passed from longjmp to setjmp. After longjmp is completed, program execution  continues as if the corresponding invocation of setjmp had just returned. If the  value passed to longjmp is 0, setjmp will behave as if it had returned 1;  otherwise, it will behave as if it had returned value。

下面我再用自己的理解把上面這段英文解釋一下:

setjmp 函數(shù)

功能:把執(zhí)行這個(gè)函數(shù)時(shí)的各種上下文信息保存起來(lái),主要就是一些寄存器的值;

參數(shù):用來(lái)保存上下文信息的緩沖區(qū),相當(dāng)于把當(dāng)前的上下文信息拍一個(gè)快照保存起來(lái);

返回值:有 2 種返回值,如果是直接調(diào)用 setjmp 函數(shù)時(shí),返回值是 0;如果是調(diào)用 longjmp 函數(shù)跳轉(zhuǎn)過(guò)來(lái)時(shí),返回值是非 0;  這里可以與創(chuàng)建進(jìn)程的函數(shù) fork 進(jìn)行一下類比。

longjmp 函數(shù)

功能:跳轉(zhuǎn)到參數(shù) env 緩沖區(qū)中保存的上下文(快照)中去執(zhí)行;

參數(shù):env 參數(shù)指定跳轉(zhuǎn)到哪個(gè)上下文中(快照)去執(zhí)行, value 用來(lái)給 setjmp 函數(shù)提供返回判斷信息,也就是說(shuō):調(diào)用 longjmp  函數(shù)時(shí),這個(gè)參數(shù) value 將會(huì)作為 setjmp 函數(shù)的返回值;

返回值:沒(méi)有返回值。因?yàn)樵谡{(diào)用這個(gè)函數(shù)時(shí),就直接跳轉(zhuǎn)到其他地方的代碼去執(zhí)行了,不會(huì)再回來(lái)了。

小結(jié):這 2 個(gè)函數(shù)是配合使用的,用來(lái)實(shí)現(xiàn)程序的跳轉(zhuǎn)。

3. setjmp:保存上下文信息

我們知道,C 代碼在編譯成二進(jìn)制文件之后,在執(zhí)行時(shí)被加載到內(nèi)存中,CPU 按照順序到代碼段取出每一條指令來(lái)執(zhí)行。在 CPU  中有很多個(gè)寄存器,用來(lái)保存當(dāng)前的執(zhí)行環(huán)境,比如:代碼段寄存器CS、指令偏移量寄存器IP,當(dāng)然了還有其他很多其它寄存器,我們把這個(gè)執(zhí)行環(huán)境稱作上下文。

CPU 在獲取下一條執(zhí)行指令時(shí),通過(guò) CS 和 IP 這 2 個(gè)寄存器就能獲取到需要執(zhí)行的指令,如下圖:

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程

補(bǔ)充一下知識(shí)點(diǎn):

上圖中,把代碼段寄存器 CS 當(dāng)做一個(gè)基地址來(lái)看待了,也就是說(shuō):CS 指向代碼段在內(nèi)存中的開始地址,IP  寄存器代表下一個(gè)要執(zhí)行的指令地址距離這個(gè)基地址的偏移量。因此每次取指令時(shí),只需要把這 2 個(gè)寄存器中的值相加,就得到了指令的地址;

其實(shí),在 x86 平臺(tái)上,代碼段寄存器 CS 并不是一個(gè)基地址,而是一個(gè)選擇子。在操作系統(tǒng)的某個(gè)地方有一個(gè)表格,這個(gè)表格里存儲(chǔ)了代碼段真正的開始地址,而  CS 寄存器中 只是存儲(chǔ)了一個(gè)索引值,這個(gè)索引值指向這個(gè)表格中的某個(gè)表項(xiàng),這里涉及到虛擬內(nèi)存的相關(guān)知識(shí)了;

IP 寄存器在獲取一條指令之后,自動(dòng)往下移動(dòng)到下一個(gè)指令的開始位置,至于移動(dòng)多少個(gè)字節(jié),那就要看當(dāng)前取出的這條指令占用了多少個(gè)字節(jié)。

CPU 是一個(gè)大傻瓜,它沒(méi)有任何的想法,我們讓它干什么,它就干什么。比如取指令:我們只要設(shè)置 CS 和 IP 寄存器,CPU 就用這 2  個(gè)寄存器里的值去獲取指令。如果把這 2 個(gè)寄存器設(shè)置為一個(gè)錯(cuò)誤的值,CPU 也會(huì)傻不拉幾的去取指令,只不過(guò)在執(zhí)行時(shí)就會(huì)崩潰。

我們可以簡(jiǎn)單的把這些寄存器信息理解為上下文信息,CPU 就根據(jù)這些上下文信息來(lái)執(zhí)行。因此,C 語(yǔ)言為我們準(zhǔn)備了 setjmp  這個(gè)庫(kù)函數(shù)來(lái)把當(dāng)前的上下文信息保存起來(lái),暫時(shí)存儲(chǔ)到一個(gè)緩沖區(qū)中。

保存的目的是什么?為了在以后可以恢復(fù)到當(dāng)前這個(gè)地方繼續(xù)執(zhí)行。

還有一個(gè)更簡(jiǎn)單的例子:服務(wù)器中的快照??煺盏淖饔檬鞘裁?當(dāng)服務(wù)器出現(xiàn)錯(cuò)誤時(shí),可以恢復(fù)到某個(gè)快照!

4. longjmp: 實(shí)現(xiàn)跳轉(zhuǎn)

說(shuō)到跳轉(zhuǎn),腦袋中立刻跳出的概念就是 goto 語(yǔ)句,我發(fā)現(xiàn)很多教程都對(duì) goto  語(yǔ)句很有意見,認(rèn)為在代碼中應(yīng)該盡量不要使用它。這樣的觀點(diǎn)出發(fā)點(diǎn)是好的:如果 goto 使用太多,會(huì)影響對(duì)代碼執(zhí)行順序的理解。

但是如果看一下 Linux 內(nèi)核的代碼,可以發(fā)現(xiàn)很多的 goto 語(yǔ)句。還是那句話:在代碼維護(hù)和執(zhí)行效率上要尋找一個(gè)平衡點(diǎn)。

跳轉(zhuǎn)改變了程序的執(zhí)行序列,goto 語(yǔ)句只能在函數(shù)內(nèi)部進(jìn)行跳轉(zhuǎn),如果是跨函數(shù)它就無(wú)能為力了。

因此,C 語(yǔ)言中為我們提供了 longjmp 函數(shù)來(lái)實(shí)現(xiàn)遠(yuǎn)程跳轉(zhuǎn),從它的名字就可以額看出來(lái),也就是說(shuō)可以跨函數(shù)跳轉(zhuǎn)。

從 CPU 的角度看,所謂的跳轉(zhuǎn)就是把上下文中的各種寄存器設(shè)置為某個(gè)時(shí)刻的快照,很顯然,上面的 setjmp  函數(shù)中,已經(jīng)把那個(gè)時(shí)刻的上下文信息(快照)存儲(chǔ)到一個(gè)臨時(shí)緩沖區(qū)中了,如果要跳轉(zhuǎn)到那個(gè)地方去接著執(zhí)行,直接告訴 CPU 就行了。

怎么告訴 CPU 呢?就是把臨時(shí)緩沖區(qū)中的這些寄存器信息覆蓋掉 CPU 中使用的那些寄存器即可。

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程

5. setjmp:返回類型和返回值

在某些需要多進(jìn)程的程序中,我們經(jīng)常使用 fork 函數(shù)來(lái)從當(dāng)前的進(jìn)程中"孵化"一個(gè)新的進(jìn)程,這個(gè)新進(jìn)程從 fork 這個(gè)函數(shù)的下一條語(yǔ)句開始執(zhí)行。

對(duì)于主進(jìn)程來(lái)說(shuō),調(diào)用 fork 函數(shù)之后返回,也是繼續(xù)執(zhí)行下一條語(yǔ)句,那么如何來(lái)區(qū)分是主進(jìn)程還是新進(jìn)程呢? fork  函數(shù)提供了一個(gè)返回值給我們來(lái)進(jìn)行區(qū)分:

fork 函數(shù)返回 0:代表這是新進(jìn)程;

fork 函數(shù)返回非 0:代表是原來(lái)的主進(jìn)程,返回?cái)?shù)值是新進(jìn)程的進(jìn)程號(hào)。

類似的,setjmp 函數(shù)也有不同的返回類型。也許用返回類型來(lái)表述不太準(zhǔn)確,可以這樣理解:從 setjmp 函數(shù)返回,一共有 2 個(gè)場(chǎng)景:

主動(dòng)調(diào)用 setjmp 時(shí):返回 0,主動(dòng)調(diào)用的目的是為了保存上下文,建立快照。

通過(guò) longjmp 跳轉(zhuǎn)過(guò)來(lái)時(shí):返回非 0,此時(shí)的返回值是由 longjmp 的第二個(gè)參數(shù)來(lái)指定的。

根據(jù)以上這 2 種不同的值,我們就可以進(jìn)行不同的分支處理了。當(dāng)通過(guò) longjmp 跳轉(zhuǎn)返回的時(shí)候,可以根據(jù)實(shí)際場(chǎng)景,返回不同的非 0 值。有過(guò)  Python、Lua 等腳本語(yǔ)言編程經(jīng)驗(yàn)的小伙伴,是不是想到了 yield/resume 函數(shù)?它們?cè)趨?shù)、返回值上的外在表現(xiàn)是一樣的!

小結(jié):到這里,基本上把 setjmp/longjmp 這 2  個(gè)函數(shù)的使用方法講完了,不知道我描述的是否足夠清楚。此時(shí),再看一下文章開頭的示例代碼,應(yīng)該一目了然了。

三、利用 setjmp/longjmp 實(shí)現(xiàn)異常捕獲

既然 C  函數(shù)庫(kù)給我們提供了這個(gè)工具,那就肯定存在一定的使用場(chǎng)景。異常捕獲在一些高級(jí)語(yǔ)言中(Java/C++),直接在語(yǔ)法層面進(jìn)行了支持,一般就是 try-catch  語(yǔ)句,但是在 C 語(yǔ)言中需要自己去實(shí)現(xiàn)。

我們來(lái)演示一個(gè)最簡(jiǎn)單的異常捕獲模型,代碼一共 56 行:

#include  #include  #include  #include   typedef int     BOOL; #define TRUE    1 #define FALSE   0  // 枚舉:錯(cuò)誤代碼 typedef enum _ErrorCode_ {     ERR_OK = 100,         // 沒(méi)有錯(cuò)誤     ERR_DIV_BY_ZERO = -1  // 除數(shù)為 0 } ErrorCode;  // 保存上下文的緩沖區(qū) jmp_buf gExcptBuf;  // 可能發(fā)生異常的函數(shù) typedef int (*pf)(int, int); int my_div(int a, int b) {     if (0 == b)     {         // 發(fā)生異常,跳轉(zhuǎn)到函數(shù)執(zhí)行之前的位置         // 第2個(gè)參數(shù)是異常代碼         longjmp(gExcptBuf, ERR_DIV_BY_ZERO);     }     // 沒(méi)有異常,返回正確結(jié)果     return a / b; }  // 在這個(gè)函數(shù)中執(zhí)行可能會(huì)出現(xiàn)異常的函數(shù) int try(pf func, int a, int b) {     // 保存上下文,如果發(fā)生異常,將會(huì)跳入這里     int ret = setjmp(gExcptBuf);     if (0 == ret)     {         // 調(diào)用可能發(fā)生異常的哈數(shù)         func(a, b);         // 沒(méi)有發(fā)生異常         return ERR_OK;     }     else     {         // 發(fā)生了異常,ret 中是異常代碼         return ret;     } }  int main() {     int ret = try(my_div, 8, 0);     // 會(huì)發(fā)生異常     // int ret = try(my_div, 8, 2);  // 不會(huì)發(fā)生異常     if (ERR_OK == ret)     {         printf("try ok ! \n");     }     else     {         printf("try excepton. error = %d \n", ret);     }          return 0; }

代碼就不需要詳細(xì)說(shuō)明了,直接看代碼中的注釋即可明白。這個(gè)代碼僅僅是示意性的,在生產(chǎn)代碼中肯定需要更完善的包裝才能使用。

有一點(diǎn)需要注意:setjmp/longjmp 僅僅是改變了程序的執(zhí)行順序,應(yīng)用程序自己的一些數(shù)據(jù)如果需要回滾的話,需要我們自己手動(dòng)處理。

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程

四、利用 setjmp/longjmp 實(shí)現(xiàn)協(xié)程

1. 什么是協(xié)程

在 C 程序中,如果需要并發(fā)執(zhí)行的序列一般都是用線程來(lái)實(shí)現(xiàn)的,那么什么是協(xié)程呢?維基百科對(duì)于協(xié)程的解釋是:

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程

更詳細(xì)的信息在這個(gè)頁(yè)面 協(xié)程,網(wǎng)頁(yè)中具體描述了協(xié)程與線程、生成器的比較,各種語(yǔ)言中的實(shí)現(xiàn)機(jī)制。

我們用生產(chǎn)者和消費(fèi)者來(lái)簡(jiǎn)單體會(huì)一下協(xié)程和線程的區(qū)別:

2. 線程中的生產(chǎn)者和消費(fèi)者

生產(chǎn)者和消費(fèi)者是 2 個(gè)并行執(zhí)行的序列,通常用 2 個(gè)線程來(lái)執(zhí)行;

生產(chǎn)者在生產(chǎn)商品時(shí),消費(fèi)者處于等待狀態(tài)(阻塞)。生產(chǎn)完成后,通過(guò)信號(hào)量通知消費(fèi)者去消費(fèi)商品;

消費(fèi)者在消費(fèi)商品時(shí),生產(chǎn)者處于等待狀態(tài)(阻塞)。消費(fèi)結(jié)束后,通過(guò)信號(hào)量通知生產(chǎn)者繼續(xù)生產(chǎn)商品。

3. 協(xié)程中的生產(chǎn)者和消費(fèi)者

生產(chǎn)者和消費(fèi)者在同一個(gè)執(zhí)行序列中執(zhí)行,通過(guò)執(zhí)行序列的跳轉(zhuǎn)來(lái)交替執(zhí)行;

生產(chǎn)者在生產(chǎn)商品之后,放棄 CPU,讓消費(fèi)者執(zhí)行;

消費(fèi)者在消費(fèi)商品之后,放棄 CPU,讓生產(chǎn)者執(zhí)行;

4. C 語(yǔ)言中的協(xié)程實(shí)現(xiàn)

這里給出一個(gè)最最簡(jiǎn)單的模型,通過(guò) setjmp/longjmp  來(lái)實(shí)現(xiàn)協(xié)程的機(jī)制,主要是目的是來(lái)理解協(xié)程的執(zhí)行序列,沒(méi)有解決參數(shù)和返回值的傳遞問(wèn)題。

typedef int     BOOL; #define TRUE    1 #define FALSE   0  // 用來(lái)存儲(chǔ)主程和協(xié)程的上下文的數(shù)據(jù)結(jié)構(gòu) typedef struct _Context_ {     jmp_buf mainBuf;     jmp_buf coBuf; } Context;  // 上下文全局變量 Context gCtx;  // 恢復(fù) #define resume() \     if (0 == setjmp(gCtx.mainBuf)) \     { \         longjmp(gCtx.coBuf, 1); \     }  // 掛起 #define yield() \     if (0 == setjmp(gCtx.coBuf)) \     { \         longjmp(gCtx.mainBuf, 1); \     }  // 在協(xié)程中執(zhí)行的函數(shù) void coroutine_function(void *arg) {     while (TRUE)  // 死循環(huán)     {         printf("\n*** coroutine: working \n");         // 模擬耗時(shí)操作         for (int i = 0; i < 10; ++i)         {             fprintf(stderr, ".");             usleep(1000 * 200);         }         printf("\n*** coroutine: suspend \n");                  // 讓出 CPU         yield();     } }  // 啟動(dòng)一個(gè)協(xié)程 // 參數(shù)1:func 在協(xié)程中執(zhí)行的函數(shù) // 參數(shù)2:func 需要的參數(shù) typedef void (*pf)(void *); BOOL start_coroutine(pf func, void *arg) {     // 保存主程的跳轉(zhuǎn)點(diǎn)     if (0 == setjmp(gCtx.mainBuf))     {         func(arg); // 調(diào)用函數(shù)         return TRUE;     }      return FALSE; }  int main() {     // 啟動(dòng)一個(gè)協(xié)程     start_coroutine(coroutine_function, NULL);          while (TRUE) // 死循環(huán)     {         printf("\n=== main: working \n");          // 模擬耗時(shí)操作         for (int i = 0; i < 10; ++i)         {             fprintf(stderr, ".");             usleep(1000 * 200);         }          printf("\n=== main: suspend \n");                  // 放棄 CPU,讓協(xié)程執(zhí)行         resume();     }      return 0; }

打印信息如下:

怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程

如果想深入研究 C 語(yǔ)言中的協(xié)程實(shí)現(xiàn),可以看一下達(dá)夫設(shè)備這個(gè)概念,其中利用 goto 和 switch  語(yǔ)句來(lái)實(shí)現(xiàn)分支跳轉(zhuǎn),其中使用的語(yǔ)法比較怪異、但是合法。

關(guān)于“怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


當(dāng)前文章:怎么用C語(yǔ)言的Setjmp和Longjmp實(shí)現(xiàn)異常捕獲和協(xié)程-創(chuàng)新互聯(lián)
本文地址:http://weahome.cn/article/dihjcd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部