女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

goto和longjmp函數的使用

CHANBAEK ? 來源:嵌入式大本營 ? 作者:小小飛飛哥 ? 2023-05-23 15:50 ? 次閱讀

分享一下在C程序設計當中對異常的處理。主要是介紹一下goto和longjmp函數的使用。

在寫程序的時候,有些地方很容易出錯,當然這種出錯不是說那種你寫錯了,而是說比如硬件的初始化失敗了,或者資源暫時不可用等等導致函數返回異常。這種錯是難以避免的,而且通常是非致命的,只要多嘗試幾次可能就可以了。比如之前我們寫過網絡編程,要建立網絡通信,我們需要調用socket,bind,listen等等一系列函數,每個函數都有可能會出錯。

但是你的程序怎么知道該怎么處理呢?程序出錯了顯然是不能繼續往下執行的,但是立即終止也不合適,因為這種錯是非致命的,那么我們應該怎么去設計一個比較健壯的程序呢?今天介紹的可以當做是一種思路。

一、使用goto

說到goto,可能很多人的第一反應是不要用,但是問他為什么他可能講不出來,因為是別人告訴他的。goto真的不能用嗎?當然不是,最有力的證明就是Linux內核里面就有大量的goto語句。實際上,只要用的適當,還是非常好用的,當然我并不是說程序里面goto滿天飛。

下面舉例說明goto的應用場景:

有時候我們完成一件事情要分為很多個步驟,每個步驟里面還可能占用一些資源,然而這些步驟很容易出錯,如果其中某個步驟出錯了,就不能繼續下一個步驟,也不能立即終止程序,因為這樣會使資源得不到釋放。那么使用goto就可以調出程序并且對資源進行回收。

來看一段代碼:

#include 
#include 
char *p1=NULL,*p2=NULL,*p3=NULL;


int step1(void);
int step2(void);
int step3(void);


int main(int argc,char* argv[])
{
  if(step1()<0)
  {
    goto error1;
  }
  if(step2()<0)
  {
    goto error2;
  }
  if(step3()<0)
  {
    goto error3;
  }
error3:
  printf("釋放步驟3的資源\\n");
  free(p3);
error2:
  printf("釋放步驟2的資源\\n");
  free(p2);
error1:
  printf("釋放步驟1的資源\\n");
  free(p1);
  exit(0);
}




int step1(void)
{
  p1=(char*)malloc(10);
  return 0;
}


int step2(void)
{
  p2=(char*)malloc(10);
  return 0;
}


int step3(void)
{
  p3=(char*)malloc(10);
  return -1;
}

在這段代碼里面,假設完成一件事情一共有三個步驟,每個步驟里面都維護了一個指針變量(資源),假設步驟一和步驟二都是正常的,步驟三出了問題,返回一個錯誤的值,如果我們接收到步驟三的錯誤返回值之后立即終止程序,那么步驟一和步驟二里申請的資源就得不到釋放,比如這里的指針會造成內存泄漏,顯然不是我們希望看到的。

但是使用上面的這種結構,如果在步驟二出錯了,它會跳轉到error2這里先釋放步驟2申請的資源,再釋放步驟一 的資源,最后退出,其他的地方出錯也是類似處理。上面是一種代碼框架,實際寫代碼應該根據實際情況來處理異常。

我們來看一下效果:

圖片

以上就是goto在多個步驟容易出錯時的一種處理。這里順便提一下goto的另外一種應用場景,就是用來跳出多層循環。我們知道跳出循環一般使用break和continue,但是這個只能調出當前循環,不能跳出多層循環,有時候在多層循環里面,一旦條件滿足,我們就不需要再執行后面的循環了,使用goto可以解決這個問題。

我們來看一下代碼:

#include 


int main(void)
{
  for(int i=0;i<2;i++)
  {
    for(int j=0;j<2;j++)
    {
      for(int k=0;k<2;k++)
      {
        if(k==1)
        {
          goto lable;
        }
lable2:        printf("i=%d,j=%d,k=%d\\n",i,j,k);
      }

    }
  }
lable:
  printf("after goto \\n");
//  goto lable2;
}

在這里有三層循環嵌套,一旦條件滿足,就通過goto跳出整個循環體,執行后面的代碼。如果使用break ,就非常麻煩。

代碼的執行結果是:

圖片

第一次k=0,正常打印,第二次,k=1,滿足條件,跳出循環,執行后面的語句,打印出after goto.

當然,問題也快出來了,剛剛是上面跳到了下面,如果我們再從下面跳上去會怎么樣?我們打開最后一行的注釋,重新編譯執行,會發現打印出幾百上千行的內容:

圖片

代碼看起來好像不復雜,就是先跳下去,然后又跳回原來的后面,怎么會打印這么多東西呢?這就是使用goto不當帶來的害處。這種交叉式地跳來跳去會使得程序結構非常混亂,混亂到我也懶得去分析。

二、使用longjmp

剛剛講了goto的異常處理,但是goto有一個局限性,就是goto只能在一個函數內進行跳轉,不能跨越函數。

如果一個函數里嵌套了多個函數調用,而里層的函數出了錯,希望跳轉到上一層或上幾層的函數,該怎么辦?顯然,goto是做不到的。這時可以使用longjmp函數。longjmp函數和setjmp函數配合使用。

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

先在程序容易出錯的地方使用setjmp,定義一個入口,等到后面代碼真的出錯之后使用longjmp跳轉到setjmp處。setjmp直接調用返回0,若從longjmp返回,則為非0.

舉個例子:

#include 
#include 


jmp_buf jmpbuffer;


int fun1(void);
int fun2(void);
int fun3(void);


int main(int argc,char* argv[])
{
  printf("這里是主函數\\n");
  if(setjmp(jmpbuffer)!=0)
  {
    printf("Error\\n");
  }
  fun1();          //假設fun1是一個容易出錯的函數,出錯后將返回上一步,然后再重新執行。
  printf("這里是主函數調用fun1之后\\n");
  return 0;
}


int fun1(void)
{
  printf("這里是fun1\\n");
  fun2();
}


int fun2(void)
{
  printf("這里是fun2\\n");
  fun3();
}


int fun3(void)
{
  static int i=0;
  printf("這里是fun3\\n");
  if(i++==0)
  {
    longjmp(jmpbuffer,1);   //跳轉回main函數
  }
  return -1;
}

在這里,主函數調用了fun1函數,而fun1調用fun2,fun2又調用fun3.這種多層嵌套里面,每一層都可能出錯。如果我們希望里面任何一層出錯了,就返回main函數,那么用longjmp就可以實現。對上面程序進行解釋:

當第一次執行setjmp時,由于是直接調用,所以返回0,接著調用我們的功能函數fun1,假設fun3里面出錯了,那么就會通過longjmp跳轉到setjmp處,同時攜帶一個返回值1,那么這時就會執行if語句進行錯誤處理,接著再執行fun1,也許此時就全部正常了,一直執行到最后。(這是很正常的現象,正如開頭說的,像硬件初始化,申請資源等都可能不是一次成功的,需要重復多次)。

而且在多個地方都可以使用longjmp,攜帶不同的返回值,這樣根據setjmp的返回值也很容易確定問題出在哪里。

來看一下效果:

圖片

使用longjmp還有一個問題我們可能也需要關注一下,就是當使用longjmp返回的時候,函數里的那些變量還能保持原來的值嗎?我們可以做一個實驗來驗證這一點:

#include 
#include 
#include 


static void f1(int,int,int,int);
static void f2(void);


static jmp_buf jmpbuffer;
static int global;


int main(int argc,char* argv[])
{
  int autoval;
  register int regival;
  volatile int volaval;
  static int staval;
  global=1;autoval=2;regival=3;volaval=4;staval=5;
  if(setjmp(jmpbuffer)!=0)
  {
    printf("after longjmp:\\n");
    printf("global=%d,autoval=%d,regival=%d,volaval=%d,staval=%d\\n", \\
      global,autoval,regival,volaval,staval);
    exit(0);
  }
  global=10;autoval=20;regival=30;volaval=40;staval=50;
  f1(autoval,regival,volaval,staval);
  exit(0);
}


static void f1(int a,int b,int c,int d)
{
  printf("in f1():\\n");
  printf("global=%d,autoval=%d,regival=%d,volaval=%d,staval=%d\\n", \\
    global,a,b,c,d);
  f2();
}


static void f2(void)
{
  longjmp(jmpbuffer,1);
}

這里我們定義了很多種不同的變量,先對變量賦一個初值,然后改變變量的值,接著調用f1,在f1里打印各變量的值,f1再調用f2,f2使用longjmp跳轉回main函數,那么這時各變量的值如何?是剛開始賦的初值,還是后面改變后的值呢?

我們編譯執行一下:

圖片

可以發現使用register聲明的變量保持的是初值,而其他變量都是改變后的值。

如果編譯時進行優化,結果又如何?

圖片

可以發現除了剛剛的register聲明的變量,普通局部變量(自動變量)也沒有更新,而是保持了初值,這通常不是我們希望的,我們肯定是希望得到最新的值,這也是因為編譯優化帶來的問題。所以如果希望避免這個問題,可以加上volatile來修飾。

以上就是今天要分享的內容,主要是在C程序中,由多個步驟可能引發的錯誤,或者是多層嵌套里面可能出現的錯誤進行處理,還要注意資源的回收等問題。附帶講了亂用goto帶來的弊端,以及在函數間跳轉與返回時變量的值的改變,程序優化帶來的影響等。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 函數
    +關注

    關注

    3

    文章

    4367

    瀏覽量

    64078
  • 代碼
    +關注

    關注

    30

    文章

    4886

    瀏覽量

    70209
  • 程序設計
    +關注

    關注

    3

    文章

    262

    瀏覽量

    30827
  • C程序
    +關注

    關注

    4

    文章

    255

    瀏覽量

    36562
收藏 人收藏

    評論

    相關推薦
    熱點推薦

    MDK中不能使用Goto Definition Goto Reference的解決方法

    變量定義的類型及定義的位置,我們僅需選中該變量然后右擊鼠標,在彈出的菜單中選中Goto Definition即可自動打開定義該變量的源文件及其所在位置;如果我們需要查找某個函數申明的原型,我們僅需要選中
    發表于 08-02 10:11

    MDK中不能使用Goto Definition Goto Reference的解決方法

    Definition即可自動打開定義該變量的源文件及其所在位置。www.11hero.com如果我們需要查找某個函數申明的原型,我們僅需要選中該函數名然后右擊鼠標,在彈出的菜單中選中Goto
    發表于 12-11 21:38

    關于linux中非局部跳轉的簡單使用

    也是有局限性的,那就是goto語句是一個實現局部跳轉的關鍵字,也就是只能在一個函數中進行跳轉,它是無法在不同的函數中實現跳轉的。那么如何實現在不同的函數中進行非局部的跳轉呢?而setj
    發表于 10-24 16:42

    關于Linux中非局部跳轉的簡單使用

    代碼邏輯容易混亂,大大降低了代碼的可讀性和可維護性。而且即使使用goto語句進行跳轉也是有局限性的,那就是goto語句是一個實現局部跳轉的關鍵字,也就是只能在一個函數中進行跳轉,它是無法在不同的
    發表于 05-16 11:40

    如何實現setjmp和longjmp的跳轉

    想實現這么一個功能,我在定時中斷中判斷一個輸入點P1.0,當P1.0接收到高電平時進入跳轉,轉到一個函數。下面代碼void Timer1_Interrupt()interrupt 3{ TH1=xx
    發表于 07-22 01:32

    【微信精選】為什么在C語言中,goto這么不受待見?

    控制。該類語句允許程序員對當前代碼行斷行,而直接進入另一個不同的代碼段。列表1為簡單的示例。編程語言終究開始引入了函數的概念,即允許程序對代碼進行斷行。如果已經完成,不再使用goto語句來表示代碼
    發表于 09-11 07:30

    setjmp、longjmp原理探究及實現

    setjmp、longjmp原理探究及實現一、原理1、實驗2、猜想二、實現三、調用四、總結一、原理C語言中包含頭文件即可實現跨函數跳轉,通常用于異常處理,在運行代碼出現異常時可以自動跳轉到調用
    發表于 01-25 07:08

    淺談C語言goto語句的用法

    冒號(:)。語句標號起標識語句的作用,與goto 語句配合使用。執行goto語句后,程序將跳轉到該標號處并執行其后的語句。另外標號必須與goto語句同處于一個函數中,但可以不在一個循環
    發表于 05-06 09:16

    Proteus之goto語句的應用

    Proteus之goto語句的應用,很好的Proteus資料,快來學習吧。
    發表于 04-18 14:49 ?0次下載

    C語言中的goto語句怎么用?為什么反對使用

    關于C語言的goto語句存在很多爭議,很多書籍都建議“謹慎使用,或者根本不用”。這里先不做過多的討論,存在即合理,既然是C語言中的一個知識點,我們還是有必要學會使用。先看一些goto如何用:
    的頭像 發表于 01-24 17:38 ?6918次閱讀
    C語言中的<b class='flag-5'>goto</b>語句怎么用?為什么反對使用

    C語言中到底應不應該使用goto語句

    關于C語言的goto語句存在很多爭議,很多書籍都建議“謹慎使用,或者根本不用”。這里先不做過多的討論,存在即合理,既然是C語言中的一個知識點,我們還是有必要學會使用。先看一些goto如何用:
    的頭像 發表于 01-16 09:12 ?7000次閱讀

    goto的優缺點

    很多書籍都會把goto當成反面教材使用,認為如果使用不當,將會造成很多意想不到的問題。但goto作為C語言的一部分,存在即合理,goto有它的缺點,也有它的優點。 缺點: 很容易把邏輯弄亂,增加理解
    的頭像 發表于 09-29 14:27 ?4380次閱讀

    Cortex-M單片機中 setjmp、longjmp原理探究及實現

    setjmp、longjmp原理探究及實現一、原理1、實驗2、猜想二、實現三、調用四、總結一、原理C語言中包含頭文件 <setjump.h> 即可實現跨函數跳轉,通常
    發表于 12-01 11:36 ?15次下載
    Cortex-M單片機中 setjmp、<b class='flag-5'>longjmp</b>原理探究及實現

    深入探討嵌入式C編程的goto語句

    什么是goto語句? goto 語句被稱為 C 語言中的跳轉語句。 用于無條件跳轉到其他標簽。它將控制權轉移到程序的其他部分。 goto 語句一般很少使用,因為它使程序的可讀性和復雜性變得更差。
    發表于 01-21 10:41 ?844次閱讀
    深入探討嵌入式C編程的<b class='flag-5'>goto</b>語句

    GoTo否認與Grab合并傳聞

    印尼網約車巨頭GoTo于當地時間2月4日正式提交文件,明確否認同競爭對手Grab的合并傳聞。該公司強調,在未來12個月內,除實施股票回購計劃外,并無任何重大行動計劃。 此前,市場上有消息稱Grab
    的頭像 發表于 02-06 10:08 ?543次閱讀