2015年9月21日 星期一

在 c 中使用類似 c++ 的 exception handle 機制

Exceptions in C with Longjmp and Setjmp 這篇文章已經把標題解釋完了, 要理解該篇文章, 需要瞭解 setjmp/longjmp 的使用方式。

那 ... 我要寫些什麼呢?

文章的 macro 可能會讓你不好理解 (我也是), 所以我列出了展開 macro 的程式碼, ex.c L25 ~ 59 的 f1 就是這樣的展現。很正常的 c 程式碼, 一點也不令人害怕, 但是一看 f2, 就會覺得神奇, 而 f1, f2 是一樣的東西, 只不過 f2 用 macro 包裝起來。

另外那個 FINALLY 的版本很神奇的把 while(1) 放到 case 0 裡頭, 真是厲害。

使用類似 exception 的錯誤處理有什麼好處呢? 你是不是覺得每次都要去檢查 malloc 的傳回值很麻煩, 希望有錯的時候丟出 exception 就好了, L13 的 mymalloc 就是在模擬這樣的行為, 這樣就不用每次都去檢查 malloc 是不是傳回 NULL 了。

ex.c
 1 // ref: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
 2 #include <stdio.h>
 3 #include <setjmp.h>
 4 #include <stdlib.h>
 5 
 6 #define TRY do { switch((ex_code = setjmp(ex_buf__))) { case 0:
 7 #define CATCH(x) break; case x : 
 8 #define ETRY break; } } while(0);
 9 #define THROW(x) longjmp(ex_buf__, x)
10 
11 jmp_buf ex_buf__; 
12 
13 void *mymalloc(size_t size)
14 {
15   static int t;
16   void *mem = malloc(size);
17   if (t >= 2)
18     mem = NULL;
19   if (mem == NULL)
20     longjmp(ex_buf__, 1); // THROW
21   ++t;
22   return mem;
23 }
24 
25 int f1()
26 {
27   int ex_code = 0;
28   do
29   { 
30     switch((ex_code = setjmp(ex_buf__)))
31     { 
32       case 0: // TRY
33       {
34         printf("In Try Statement\n");
35         
36         char *mem = (char *)mymalloc(32);
37         mem = (char *)mymalloc(32);
38         mem = (char *)mymalloc(32);
39         printf("I do not appear\n");
40       }
41 
42       break; 
43       case 1 : // CATCH(x)
44       {
45         printf("mem alloc fail! %d\n", ex_code);
46       }
47 
48       break; 
49       case 5 : // CATCH(x)
50       {
51         printf("Got Exception! %d\n", ex_code);
52       }
53 
54       break; 
55     }
56   } while(0); // ETRY
57 
58   return 0;
59 }
60 
61 int f2()
62 {
63   int ex_code = 0;
64       TRY
65       {
66         printf("In Try Statement\n");
67         
68         THROW(5);
69         printf("I do not appear\n");
70       }
71       CATCH(1)
72       {
73         printf("Got Exception! %d\n", ex_code);
74       }
75       CATCH(5)
76       {
77         printf("Got Exception! %d\n", ex_code);
78       }
79       ETRY
80 
81   return 0;
82 }
83 
84 int main(int argc, char *argv[])
85 {
86   f1(); 
87   f2(); 
88   return 0;
89 }


z.c L12 的 while(1) 真是神奇的用法, 我都不知道要怎麼排版了。

z.c
 1 #include <stdio.h>
 2 #include <setjmp.h>
 3 
 4 int main(int argc, char **argv)
 5 {
 6   do
 7   {
 8     jmp_buf ex_buf__;
 9     switch (_setjmp(ex_buf__))
10     {
11       case 0:
12       while (1)
13       {
14         {
15           printf("In Try Statement\n");
16           longjmp(ex_buf__, (2));
17           printf("I do not appear\n");
18         }
19         break;
20         case (1):
21         {
22           printf("Got Foo!\n");
23         }
24         break;
25         case (2):
26         {
27           printf("Got Bar!\n");
28         }
29         break;
30         case (3):
31         {
32           printf("Got Baz!\n");
33         }
34         break;
35       } // end while(1)
36       default:
37       {
38         {
39           printf("...et in arcadia Ego\n");
40         }
41       }
42     }
43   }
44   while (0);
45 
46   return 0;
47 }

獲得此技能後, 我稍微改寫了 bare metal programming for stm32f4 - discovery: using c++ std::vector 的 class my_allocator, 讓它在要不到記憶體時, 丟出 exception, 其實就只是發動 longjmp 而已, 真的沒什麼了不起。

result in qemu stm32-p103
  1 descent@debian64:myvec$ ~/git/qemu_stm32/arm-softmmu/qemu-system-arm -M stm32-p103 -kernel myvec.bin  -nographic
  2 STM32_UART: UART1 clock is set to 0 Hz.
  3 STM32_UART: UART1 BRR set to 0.
  4 STM32_UART: UART1 Baud is set to 0 bits per sec.
  5 STM32_UART: UART2 clock is set to 0 Hz.
  6 STM32_UART: UART2 BRR set to 0.
  7 STM32_UART: UART2 Baud is set to 0 bits per sec.
  8 STM32_UART: UART3 clock is set to 0 Hz.
  9 STM32_UART: UART3 BRR set to 0.
 10 STM32_UART: UART3 Baud is set to 0 bits per sec.
 11 STM32_UART: UART4 clock is set to 0 Hz.
 12 STM32_UART: UART4 BRR set to 0.
 13 STM32_UART: UART4 Baud is set to 0 bits per sec.
 14 STM32_UART: UART5 clock is set to 0 Hz.
 15 STM32_UART: UART5 BRR set to 0.
 16 STM32_UART: UART5 Baud is set to 0 bits per sec.
 17 STM32_UART: UART5 clock is set to 0 Hz.
 18 STM32_UART: UART5 BRR set to 0.
 19 STM32_UART: UART5 Baud is set to 0 bits per sec.
 20 STM32_UART: UART4 clock is set to 0 Hz.
 21 STM32_UART: UART4 BRR set to 0.
 22 STM32_UART: UART4 Baud is set to 0 bits per sec.
 23 STM32_UART: UART3 clock is set to 0 Hz.
 24 STM32_UART: UART3 BRR set to 0.
 25 STM32_UART: UART3 Baud is set to 0 bits per sec.
 26 STM32_UART: UART2 clock is set to 0 Hz.
 27 STM32_UART: UART2 BRR set to 0.
 28 STM32_UART: UART2 Baud is set to 0 bits per sec.
 29 STM32_UART: UART1 clock is set to 0 Hz.
 30 STM32_UART: UART1 BRR set to 0.
 31 STM32_UART: UART1 Baud is set to 0 bits per sec.
 32 LED Off
 33 CLKTREE: HSI Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 34 CLKTREE: HSI/2 Output Change (SrcClk:HSI InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
 35 CLKTREE: SYSCLK Output Change (SrcClk:HSI InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 36 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 37 STM32_RCC: Cortex SYSTICK frequency set to 8000000 Hz (scale set to 125).
 38 STM32_RCC: Cortex SYSTICK ext ref frequency set to 1000000 Hz (scale set to 1000).
 39 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 40 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 41 CLKTREE: HSE Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 42 CLKTREE: HSE/2 Output Change (SrcClk:HSE InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
 43 CLKTREE: PLLXTPRE Output Change (SrcClk:HSE InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 44 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
 45 CLKTREE: PLLCLK Output Change (SrcClk:PLLXTPRE InFreq:8000000 OutFreq:72000000 Mul:9 Div:1 Enabled:1)
 46 CLKTREE: SYSCLK Output Change (SrcClk:PLLCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 47 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 48 STM32_RCC: Cortex SYSTICK frequency set to 72000000 Hz (scale set to 13).
 49 STM32_RCC: Cortex SYSTICK ext ref frequency set to 9000000 Hz (scale set to 111).
 50 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:36000000 Mul:1 Div:2 Enabled:1)
 51 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 52 CLKTREE: GPIOA Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 53 CLKTREE: AFIO Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 54 CLKTREE: UART2 Output Change (SrcClk:PCLK1 InFreq:36000000 OutFreq:36000000 Mul:1 Div:1 Enabled:1)
 55 STM32_UART: UART2 clock is set to 36000000 Hz.
 56 STM32_UART: UART2 BRR set to 0.
 57 STM32_UART: UART2 Baud is set to 0 bits per sec.
 58 STM32_UART: UART2 clock is set to 36000000 Hz.
 59 STM32_UART: UART2 BRR set to 3750.
 60 STM32_UART: UART2 Baud is set to 9600 bits per sec.
 61 
 62 throw ex test
 63 mymalloc: 1 byte(s)
 64 used my_allocator to allocate at address x n: 536883612 sizeof(T): 1
 65 mymalloc: 2 byte(s)
 66 used my_allocator to allocate at address x n: 536883740 sizeof(T): 2
 67 myfree: p
 68 index: 0
 69 size: 1
 70 used my_allocator to free address x
 71 mymalloc: 4 byte(s)
 72 used my_allocator to allocate at address x n: 536883868 sizeof(T): 4
 73 myfree: p
 74 index: 1
 75 size: 1
 76 used my_allocator to free address x
 77 mymalloc: 8 byte(s)
 78 used my_allocator to allocate at address x n: 536883996 sizeof(T): 8
 79 myfree: p
 80 index: 2
 81 size: 1
 82 used my_allocator to free address x
 83 mymalloc: 16 byte(s)
 84 used my_allocator to allocate at address x n: 536884124 sizeof(T): 16
 85 myfree: p
 86 index: 3
 87 size: 1
 88 used my_allocator to free address x
 89 mymalloc: 32 byte(s)
 90 used my_allocator to allocate at address x n: 536884252 sizeof(T): 32
 91 myfree: p
 92 index: 4
 93 size: 1
 94 used my_allocator to free address x
 95 mymalloc: 64 byte(s)
 96 used my_allocator to allocate at address x n: 536884380 sizeof(T): 64
 97 myfree: p
 98 index: 5
 99 size: 1
100 used my_allocator to free address x
101 mymalloc: 128 byte(s)
102 used my_allocator to allocate at address x n: 536884508 sizeof(T): 128
103 myfree: p
104 index: 6
105 size: 1
106 used my_allocator to free address x
107 mymalloc: 256 byte(s)
108 EXCEED_MEMAREA
109 not enough: free_index: 8, size: 2
110 new_index: 0
111 used my_allocator to allocate at address x n: 536883612 sizeof(T): 256
112 myfree: p
113 index: 7
114 size: 1
115 used my_allocator to free address x
116 mymalloc: 512 byte(s)
117 used my_allocator to allocate at address x n: 536883868 sizeof(T): 512
118 myfree: p
119 index: 0
120 size: 2
121 used my_allocator to free address x
122 mymalloc: 1024 byte(s)
123 EXCEED_MEMAREA
124 not enough: free_index: 6, size: 8
125 new_index: 0
126 used my_allocator to allocate at address x n: 536883612 sizeof(T): 1024
127 myfree: p
128 index: 2
129 size: 1
130 used my_allocator to free address x
131 mymalloc: 2048 byte(s)
132 EXCEED_MEMAREA
133 not enough: free_index: 8, size: 16
134 EXCEED_MEMAREA
135 EXCEED_MEMAREA
136 cannot alloc memory
137 
138 got no free mem

vec.cpp
  1 #include "mem.h"
  2 #include "k_stdio.h"
  3 #include "my_setjmp.h"
  4 
  5 #define setjmp my_setjmp
  6 #define longjmp my_longjmp
  7 
  8 #define TRY do { switch((ex_code = setjmp(ex_buf__))) { case 0:
  9 #define CATCH(x) break; case x : 
 10 #define ETRY break; } } while(0);
 11 #define THROW(x) longjmp(ex_buf__, x)
 12 
 13 #define NOFREE_MEM 5
 14 
 15 jmp_buf ex_buf__;
 16 
 17 using namespace DS; 
 18 using namespace std;
 19 
 20 #include <vector>
 21 #include <string>
 22 #include <map>
 23 
 24 void *dso_handle_;
 25 void *__dso_handle;
 26 
 27 extern "C" void _exit()
 28 {
 29 }
 30 
 31 char brk_area[10240];
 32 
 33 extern "C" char *_sbrk(char *increment)
 34 {
 35   char *ptr = brk_area;
 36   return ptr;
 37 }
 38 
 39 extern "C"
 40 int _kill(int a, int b)
 41 {
 42   return a;
 43 }
 44 
 45 extern "C"
 46 int _getpid()
 47 {
 48   int i;
 49   return i;
 50 }
 51 
 52 extern "C"
 53 int _write(int fd, const void *buf, int count)
 54 {
 55 }
 56 
 57 extern "C"
 58 int open(const char *pathname, int flags, int mode)
 59 {
 60 }
 61 
 62 extern "C"
 63 int _open(const char *pathname, int flags, int mode)
 64 {
 65 }
 66 
 67 
 68 extern "C"
 69 int _isatty(int fd)
 70 {
 71 }
 72 
 73 
 74 extern "C"
 75 int _close(int fd)
 76 {
 77 }
 78 
 79 extern "C"
 80 int _fstat(int fd, struct stat *buf)
 81 {
 82 }
 83 
 84 extern "C"
 85 int _read(int fd, void *buf, int count)
 86 {
 87 }
 88 
 89 extern "C"
 90 int _lseek(int fd, int offset, int whence)
 91 {
 92 }
 93 
 94 static char memarea[10240];
 95 
 96 void out_of_mem()
 97 {
 98   while(1);
 99 }
100 
101 template <class T>
102 class my_allocator
103 {
104 public:
105   typedef int    size_type;
106   typedef int difference_type;
107   typedef T*        pointer;
108   typedef const T*  const_pointer;
109   typedef T&        reference;
110   typedef const T&  const_reference;
111   typedef T         value_type;
112 
113   my_allocator() {}
114   my_allocator(const my_allocator&) {}
115 
116 
117 
118   pointer   allocate(size_type n, const void * = 0) {
119               T* t = (T*) mymalloc(n * sizeof(T));
120               if (t==0)
121               {
122                 printf("cannot alloc memory\n");
123                 //throw std::bad_alloc();
124                 THROW(NOFREE_MEM);
125                 //out_of_mem();
126                 //exit(-1);
127               }
128               printf("used my_allocator to allocate at address %x n: %d sizeof(T): %d", (int)t, n, sizeof(T));
129               printf("\r\n");
130 
131               return t;
132             }
133   
134   void      deallocate(void* p, size_type) {
135               if (p) {
136                 myfree(p);
137                 printf("used my_allocator to free address %x", (int)p);
138                 printf("\r\n");
139               } 
140             }
141 
142   pointer           address(reference x) const { return &x; }
143   const_pointer     address(const_reference x) const { return &x; }
144   my_allocator<T>&  operator=(const my_allocator&) { return *this; }
145   void              construct(pointer p, const T& val) 
146                     { new ((T*) p) T(val); }
147   void              destroy(pointer p) { p->~T(); }
148 
149   size_type         max_size() const { return int(-1); }
150 
151   template <class U>
152   struct rebind { typedef my_allocator<U> other; };
153 
154   template <class U>
155   my_allocator(const my_allocator<U>&) {}
156 
157   template <class U>
158   my_allocator& operator=(const my_allocator<U>&) { return *this; }
159 };
160 
178 
179 void vec_test_eh(void)
180 {
181 #if 1
182   int ex_code = 0;
183   printf("\r\nthrow ex test\r\n");
184 
185   TRY
186   {
187     std::vector<char, my_allocator<char> > vec;
188     for (int i=0 ; i < 2000 ; ++i)
189     {
190       vec.push_back(i);
191     }
192   }
193   CATCH(NOFREE_MEM)
194   {
195     printf("\r\ngot no free mem\r\n");
196   }
197   ETRY
198   while(1);
199 #endif
200 }
201 

順道題一下 g++5, g++4 的小差異, 還有 float 是 hard/soft 對編譯程式碼的影響。

g++ 5 產生的 text 部份很大, 我猜測是 g++ 搭配的 c++ library 其 vector 程式碼變大了, 這已經影響到我能用的空間了。

原本是

MEMORY
{
 FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K
 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

改成

MEMORY
{
 FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 1280K
 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 1280K
}

才能產生出 elf 執行檔。

stm32 的 flash 和 ram 很小, 塞不下了。

descent@debian64:myvec$ size myvec.elf.gcc51.fp_hard myvec.elf.gcc51.fp_soft myvec.elf.gcc48 
   text    data     bss     dec     hex filename
 475426    1501   91889  568816   8adf0 myvec.elf.gcc51.fp_hard (64 bit arm gcc 51)
 474820    1501   91889  568210   8ab92 myvec.elf.gcc51.fp_soft (32 bit arm gcc 51)
 114281    2300   89961  206542   326ce myvec.elf.gcc48

不過這只是測試用的, 我從來就沒想過要把 vector 用到自己的 os 上, 有需要我會自己寫一個 vector, 又不難, 網路上就有得抄。

一開始用的 g++ 51 是 hard fp, 結果 -mfloat-abi=soft 編譯有問題, 改成 -mfloat-abi=hard 才能編過。後來重新建立一個 g++ 51 是 soft fp 的版本, -mfloat-abi=soft 就可以編過了。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。