用c語言模擬c++的異常處理機制



原文:http://blog.chinaunix.net/uid-10275706-id-3339018.html

起因:在c語言工程代碼當中,常常要處理各種關於異常的處理,每次當程序比較複雜的時候,就必須用好幾個if來嵌套使用,比如


點擊(此處)摺疊或打開

  1. if (xxx)
  2. {
  3.      if(xxx)
  4.      {
  5.      }
  6.       else
  7.        {
  8.              return ERR_NO;
  9.        }
  10. }

這樣的代碼顯得非常混亂,也不容易管理,我一直在尋找能跟c++異常機制類似的功能,如果有這樣的功能,那麼c語言的異常處理不是也很容易打理了麼?

由於c的工程當中一般錯誤都有專有的錯誤列表,所以在這邊,在我們的機制裏我只添加了關於錯誤Id的異常捕捉處理。

思路:

首先,我使用了#define來將c當中沒有的關鍵詞替代,比如try,catch,throw,先想到的是用return,break之類的c語言當中先有的關鍵詞來模擬,這就有了我的第一個版本:


點擊(此處)摺疊或打開

  1. #ifndef EXCEPTION_H
  2. #define EXCEPTION_H
  3. // 一個不成熟的exception機制
  4. typedef unsigned int    ERR_TYPE;

  5. #define NO_ERR 0
  6. static ERR_TYPE err; //try-catch-throw錯誤處理機制

  7. #define try do
  8. #define catch(N) while(0);if(N>=NO_ERR)
  9. #define throw(N) err=N;break;
  10. #define throwAgain(N) err=N;return;

  11. #endif
調用實例代碼:


點擊(此處)摺疊或打開

  1. void f(void)
  2. {
  3.     try
  4.     {
  5.         throw(6);
  6.     }
  7.     catch(err)
  8.     {
  9.         throwAgain(err);
  10.     }
  11. }

  12. int main()
  13. {
  14.     try
  15.     {
  16.         f();
  17.     }
  18.     catch(err)
  19.     {
  20.         printf("%d", err);
  21.     }
  22. }

這個設計的程序有一個很大的問題,那就是它只能在同級的函數下使用try。

這是什麼意思呢?如果我需要這樣使用:


點擊(此處)摺疊或打開

  1. Int main()
  2. {
  3.     Try
  4.     {
  5.      f();
  6.     }
  7.     catch(err)
  8.     {
  9.         printf("%d", err);
  10.     }
  11. }
  12. Void f()
  13. {
  14. G();
  15. // some codes follows..
  16. }
  17. Void g()
  18. {
  19.     Throw(3);
  20. }

在g()throw出異常3的時候,main函數沒有辦法直接捕捉,而是必須在f()函數下面繼續執行後面的語句。就是這套設計的機制多個嵌套的函數是沒有辦法實施的。

那麼還有什麼辦法可以解決這個問題呢?Goto可以嗎?不可以,goto也只能在同級的函數下使用。goto是本地的:它只能跳到所在函數內部的標號上,而不能將控制權轉移到所在程序的任意地點 

最後找到一個方法:



點擊(此處)摺疊或打開

  1. #include <setjmp.h>

  2. int setjmp(jmp_buf env)
  3. Returns: 0 if called directly, nonzero if returning from a call to longjmp.
  4. void longjmp(jmp_buf env, int val);
setjmp 和longjmp這2個函數可以在不同的函數進行跳轉,只要設置他們的上下文,在用longjmp跳轉到這設置好的上下文當中就可以了!!

這2個函數的使用方法:


點擊(此處)摺疊或打開

  1. #include<stdio.h> 
  2. #include<setjmp.h> 
  3. jmp_buf ebuf; 
  4. void f2(void); 
  5. int main(void) 
  6. { 
  7.  int i; 
  8.  printf("1"); 
  9.  i=setjmp(ebuf); 
  10.  if(i==0)
  11.  { 
  12.    f2(); 
  13.    printf("This will not be printed."); 
  14. } 
  15.  printf("%d",i); 
  16.  return 0;
  17. } 
  18.  void f2(void)
  19. { 
  20.  printf("2"); 
  21.  longjmp(ebuf,3);
  22. }

首先,在函數a裏面setjmp,將當前函數的env都存儲起來,然後在函數b裏面調用longjmp,這樣就會直接跳轉到函數a的setjmp這個地方,longjmp的第二個值作用是在第二次返回a函數的時候setjmp會返回longjmp的第二個參數。

有了這個方法,我們的程序就好設計多了,由於設計多個函數的調用,所以設計的時候使用了鏈表來處理保存每個函數的env。

全部實現代碼如下:


點擊(此處)摺疊或打開

  1. #ifndef LIST_H
  2. #define LIST_H
  3. typedef struct list_head_tag
  4. {
  5.   struct list_head_tag *prev;
  6.   struct list_head_tag *next;
  7. }list_head;

  8. #define list_for_each(pos, head) \
  9.     for ( pos = (head)->next; \
  10.           pos != (head); \
  11.           pos = pos->next)

  12. #define list_for_each_safe(pos,pos_next,head) \
  13.     for ( pos = (head)->next, pos_next = pos->next; \
  14.           pos != (head); \
  15.           pos = pos_next, pos_next = pos->next)

  16. #define list_empty(head) (head->next == head)

  17. #define list_entry(ptr, type, member) ((type*)ptr)

  18. #define init_list_head(ptr) \
  19.     do{\
  20.         (ptr)->prev = ptr; \
  21.         (ptr)->next = ptr; \
  22.     }while(0)

  23. extern void list_add_before(list_head *node, list_head *pos);

  24. extern void list_add_after(list_head *node, list_head *pos);

  25. extern void list_del(list_head *node);
  26. #endif

點擊(此處)摺疊或打開

  1. #include "list.h"
  2. void list_add_before(list_head *node, list_head *pos)
  3. {
  4. node->prev = pos->prev;
  5. node->next = pos;
  6. pos->prev->next = node;
  7. pos->prev = node;
  8. }
  9. void list_add_after(list_head *node, list_head *pos)
  10. {
  11. node->prev = pos;
  12. node->next = pos->next;
  13. pos->next->prev = node;
  14. pos->next = node;
  15. }
  16. void list_del(list_head *node)
  17. {
  18. node->prev->next = node->next;
  19. node->next->prev = node->prev;
  20. }

點擊(此處)摺疊或打開

  1. #ifndef EXC_H
  2. #define EXC_H
  3. char err = -1;
  4. static char isJumpListInit = 0;
  5. //jmp_buf jump_buffer;
  6. typedef struct JumpBufListTag
  7. {
  8. struct list_head_tag list;
  9. jmp_buf jump_buffer;
  10. }JumpBufList, *JumpBufListPtr;
  11. JumpBufList jumplist = {NULL, NULL};
  12. JumpBufListPtr head = &jumplist;
  13. JumpBufListPtr cur = &jumplist;
  14. int SetCurJump(void)
  15. {
  16. JumpBufListPtr newPtr = (JumpBufList*)calloc(sizeof(JumpBufList));
  17. if (!isJumpListInit)
  18. {
  19. init_list_head(&head->list);
  20. isJumpListInit = 1;
  21. }
  22. list_add_after(&newPtr->list, &head->list);
  23. cur = newPtr;
  24. return 0;
  25. }
  26. void JumpCurLong(void)
  27. {
  28. longjmp(cur->jump_buffer, 1);
  29. }
  30. void DestoryCurJumpEnv( void )
  31. {
  32. list_del(&cur->list);
  33. free(cur);
  34. cur = head->list.next;
  35. }
  36. #define try SetCurJump();if(setjmp(cur->jump_buffer) == 0)
  37. #define catch(N) DestoryCurJumpEnv();if(N>=0)
  38. #define throw(N) err=N;JumpCurLong();
  39. #endif
這邊,List.h和list.c摘抄自linux內核代碼。

使用demo代碼:


點擊(此處)摺疊或打開

  1. void h()
  2. {
  3.     throw(7);
  4. }
  5. void e()
  6. {
  7.     h();
  8. }
  9. void g(void)
  10. {
  11.     try
  12.     {
  13.     e();
  14.     printf("g()3");
  15.     }
  16.     catch(err)
  17.     {
  18.         throw(err);
  19.     }
  20. }
  21. int main()
  22. {
  23.     
  24.     try
  25.     {
  26.         g();

  27.     }
  28.     catch(err)
  29.     {
  30.         printf("%d", err);
  31.     }
  32.     return 0;
  33. }
這樣,就可以實現一個簡易版本的用c語言模擬c++的錯誤處理異常的機制了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章