中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

本篇內(nèi)容主要講解“用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程”吧!

我們提供的服務(wù)有:成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、富錦ssl等。為近千家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的富錦網(wǎng)站制作公司

  •  一、前言

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

    • 與 goto 語句比較

    • 與 fork 函數(shù)比較

    • 與 Python 語言中的 yield/resume 比較

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

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

  • 五、總結(jié)

一、前言

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

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

今天,我們就來掰扯掰扯這兩個函數(shù)。雖然在一般的程序中使用不上,但是在今后的某個場合,當(dāng)你需要處理一些比較奇特的程序流程時,也許它們可以給你帶來意想不到的效果。

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

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

1. 最簡示例

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

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

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

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

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

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

2. 函數(shù)說明

首先來看下這個 2 個函數(shù)的簽名:

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

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

  • 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í)行這個函數(shù)時的各種上下文信息保存起來,主要就是一些寄存器的值;

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

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

longjmp 函數(shù)

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

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

返回值:沒有返回值。因為在調(diào)用這個函數(shù)時,就直接跳轉(zhuǎn)到其他地方的代碼去執(zhí)行了,不會再回來了。

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

3. setjmp:保存上下文信息

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

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

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

補充一下知識點:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

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

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

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

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

fork 函數(shù)返回非 0:代表是原來的主進程,返回數(shù)值是新進程的進程號。

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

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

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

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

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

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

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

我們來演示一個最簡單的異常捕獲模型,代碼一共 56 行:

#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <setjmp.h>  typedef int     BOOL; #define TRUE    1 #define FALSE   0  // 枚舉:錯誤代碼 typedef enum _ErrorCode_ {     ERR_OK = 100,         // 沒有錯誤     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個參數(shù)是異常代碼         longjmp(gExcptBuf, ERR_DIV_BY_ZERO);     }     // 沒有異常,返回正確結(jié)果     return a / b; }  // 在這個函數(shù)中執(zhí)行可能會出現(xiàn)異常的函數(shù) int try(pf func, int a, int b) {     // 保存上下文,如果發(fā)生異常,將會跳入這里     int ret = setjmp(gExcptBuf);     if (0 == ret)     {         // 調(diào)用可能發(fā)生異常的哈數(shù)         func(a, b);         // 沒有發(fā)生異常         return ERR_OK;     }     else     {         // 發(fā)生了異常,ret 中是異常代碼         return ret;     } }  int main() {     int ret = try(my_div, 8, 0);     // 會發(fā)生異常     // int ret = try(my_div, 8, 2);  // 不會發(fā)生異常     if (ERR_OK == ret)     {         printf("try ok ! \n");     }     else     {         printf("try excepton. error = %d \n", ret);     }          return 0; }

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

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

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

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

1. 什么是協(xié)程

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

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

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

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

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

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

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

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

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

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

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

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

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

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

typedef int     BOOL; #define TRUE    1 #define FALSE   0  // 用來存儲主程和協(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");         // 模擬耗時操作         for (int i = 0; i < 10; ++i)         {             fprintf(stderr, ".");             usleep(1000 * 200);         }         printf("\n*** coroutine: suspend \n");                  // 讓出 CPU         yield();     } }  // 啟動一個協(xié)程 // 參數(shù)1:func 在協(xié)程中執(zhí)行的函數(shù) // 參數(shù)2:func 需要的參數(shù) typedef void (*pf)(void *); BOOL start_coroutine(pf func, void *arg) {     // 保存主程的跳轉(zhuǎn)點     if (0 == setjmp(gCtx.mainBuf))     {         func(arg); // 調(diào)用函數(shù)         return TRUE;     }      return FALSE; }  int main() {     // 啟動一個協(xié)程     start_coroutine(coroutine_function, NULL);          while (TRUE) // 死循環(huán)     {         printf("\n=== main: working \n");          // 模擬耗時操作         for (int i = 0; i < 10; ++i)         {             fprintf(stderr, ".");             usleep(1000 * 200);         }          printf("\n=== main: suspend \n");                  // 放棄 CPU,讓協(xié)程執(zhí)行         resume();     }      return 0; }

打印信息如下:

用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程

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

五、總結(jié)

這篇文章的重點是介紹 setjmp/longjmp 的語法和使用場景,在某些需求場景中,能達(dá)到事半功倍的效果。

當(dāng)然,你還可以發(fā)揮想象力,通過執(zhí)行序列的跳轉(zhuǎn)來實現(xiàn)更加花哨的功能,一切皆有可能!

到此,相信大家對“用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

新聞名稱:用C語言的Setjmp和Longjmp實現(xiàn)異常捕獲和協(xié)程
URL網(wǎng)址:http://www.rwnh.cn/article44/ihjihe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、微信小程序、用戶體驗、網(wǎng)頁設(shè)計公司、微信公眾號、網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名
抚州市| 页游| 达孜县| 沙坪坝区| 衡阳市| 荃湾区| 江永县| 沙湾县| 西宁市| 洛浦县| 社旗县| 新源县| 普宁市| 邻水| 武平县| 德钦县| 阳原县| 乐山市| 云龙县| 永胜县| 专栏| 濉溪县| 偃师市| 华安县| 富源县| 来安县| 乐至县| 巴彦淖尔市| 贡山| 绥芬河市| 竹北市| 徐汇区| 金门县| 临海市| 抚宁县| 紫云| 昌平区| 平远县| 南郑县| 万安县| 塘沽区|