哇塞,C語言有try catch嗎?固然沒有。倒。。可能有人說了,那你野鬼說沒有的東西作什麼。
這裏須要重申一下,所謂正向設計下問題檢測的開發方法。正向設計時,在錯誤檢測和問題修復的方法是指:linux
根據源碼分析,在源碼中加插檢測代碼的方式,驗證對代碼的理解和預判是否正確。bash
而反向跟蹤是根據機器執行動做,反向理解邏輯的運行狀態,例如GDB。二者不少方面都很像,但存在一個最主要的區別在於,你是在先驗的讓程序運行判斷是否符合先驗,仍是在程序的運行中理解代碼的邏輯。socket
例如正向設計的問題判斷,若是經過try catch來處理,則表示你已經估計到這裏是個潛在可能的錯誤,而經過運行來驗證你的判斷是否成立。錯誤的理解和判斷是在對源碼的分析上,反之,經過 GDB或者其餘IDE加斷點的跟蹤,屬於反向檢測運行狀態來判斷邏輯可能哪裏出錯。
惋惜try catch在C標準裏面並無。這裏,就經過指針訪問的段錯誤,來設計一個相似try catch的小玩意。但願新手能經過這個寫出其餘的try catch。
首先咱們要動點C標準裏,signal和setjmp的東西,以及GNU C的 glibc函數sigsetjmp,siglongjmp。記得要注意哪些是標準裏的,哪些不是,這對之後的平臺移植很用做用,哪怕你不移植,但萬一別人 要移植,由於你沒有區分好,致使別人處處找和平臺相關的代碼,最後這個代碼別人用不了,你的代碼又有何價值。
先說一下signal這個函數。這函數的做用就是相似對中斷向量表的重載,實際上是個重定位工做。中斷向量表是什麼意思,很簡單,就是有一個表,表裏面有一 堆函數地址,若是對應的中斷髮生了,就跳轉到該中斷對應的中斷向量表的對應存儲區域的函數裏(但願看這句話你別跟着念,眼睛也別花,哈)。若是不重定位這 些函數,則會啓用默認的函數。例如給打印個消息,而後讓進程中止。若是經過signal 對這個中斷向量表進行」重載「,則能夠進入你指定的函數。而不會進入默認的方式。
簡單舉例吧,正常的程序,你在bash上執行 ctrl+C,會讓程序中止。實際是怎麼發生的呢?
一、鍵盤發現你按鍵了,則會發出一個信號,除了按鍵值自己。該信號是告訴CPU,我有事了,CPU的硬件若是不對這個信號視而不見的話,就會告訴OS,哦,有個硬中斷髮生。
二、此時,OS就對應的發出個軟中斷,linux下就是 SIGINT。
三、若是對應你當前進程的本身的」中斷向量表「是默認狀況,則會OS啓動一個函數,這個函數會發出一個kill的工做,要求將你這個進程結束。
四、OS此時發現這個動做。就把你的進程給卡擦了。
而你若是用signal「重載」這個中斷,則此時原先默認函數不會執行,你會發現,你的程序該作什麼仍是作什麼,除非你的新函數仍然要求kill掉你這個進程。
那麼對於段錯誤,也存在一箇中斷,是由誰產生的? MMU,硬件產生的。OS獲取這個錯誤以後,就會到對應進程的「中斷向量表」找你是否重載過這個對應的中斷響應函數,固然這中間還有些其餘工做好比中斷屏 蔽方面的檢查工做等,我就不展開了。由此,若是你寫了一個新的函數,則能夠不用退出,能夠處理些本身的想作的事情。
可是很討厭的一個問題,若是是你的進程出錯,並且出錯的理由是對一個不正確的內存空間進行讀寫操做,不管你執行多少次,這個錯誤仍然存在。由於你的代碼產 生個由MMU發出的段錯誤的中斷,此時你的代碼被強行掛起來,就是說如今輪不到你玩了,而後OS處理對應的中斷響應,就是你寫的代碼,而寫的那個函數執行 完畢後,若是不kill或者其餘動做,你的代碼還會被再次執行。那麼你的進程會怎麼執行呢?在你上次錯的地方繼續執行。。。。這就鬱悶大發了。由於再次產 生個錯誤,此時又會進入你的中斷響應函數。
因而乎,你會發現,你的屏幕在不停的打印東西,若是你的那個函數內有條printf想提醒你,進入了這個函數了。
此時,咱們就須要動點手段了。這裏要談一下setjmp ,和longjmp,最終會用到sigsetjmp,siglongjmp,注意,這兩玩意不是C標準的,glibc支持,其餘的一些C環境也支持,我不一一列出來了。能夠查資料。
先說setjmp, longjmp。上下文這個詞在OS裏,特別進程管理部分常常提到,什麼意思呢?簡單說就是現場環境,不過只是CPU裏面的,包括指令的位置(其實也是在 寄存器裏),常規寄存器裏的內容(也包括堆棧指針寄存器),和外部存儲器就是內存沒有關係。
繼續舉例吧。
假設,劇場正在上演一個話劇,還沒結束呢。結果通知,馬上清場,爲何,領導要來開會,你別問爲何,領導就是領導,優先級高,此時你怎麼辦,那就和觀衆 商量一下,咱們把現場記錄下來,臺詞說哪也記錄下來,等領導開完會我們接着看,此時劇場的狀況清點記錄完畢,並清理乾淨,等領導開會,會開完了,再根據被 打斷時的場景進行恢復,則此時觀衆能夠連貫的繼續看下去。
另外一個例子就是香港的賭王片,賭到高潮的時候,不管是正方的叛徒仍是反方的臥底,不管是用刀仍是用槍,反正把男一號給搞傷了,怎麼辦?封牌局,拿個罩子, 把桌子罩起來,誰也別想改動這些牌的內容。等男二號上時在繼續賭,牌是什麼狀況,確定沒有變過,不管中間穿插了多少其餘鏡頭。假設恰好這個時候有跑龍套的 要用桌子吃午餐,劇組贊成了,把桌子給他用,可是桌子上面的東西則原封不動的挪到別的地方。等男二號來,再把跑龍套的趕走,恢復成前面的牌局。
這裏劇場場景的記錄並切換成領導的會議桌,以及桌子上的牌局挪動,讓給跑龍套的吃午餐,都是叫作上下文切換。setjmp的做用就是保存當前進入 setjmp函數時的環境。同時setjmp返回個值爲0。以區別longjmp跳轉到當前位置。相似函數調用函數,父函數須要保存的現場工做,不然子函 數退出時,父函數也沒辦法正常繼續工做啊。是否是。固然 setjmp所保存的東西必函數調用時保存的要多一些。其實setjmp沒什麼特點。我本身都寫過對應彙編以實現特定硬件上的需求。就是一堆mov,把寄 存器的值存到指定的位置再返回0。
而longjmp的意思是,能夠在你的代碼任意的位置,只要setjmp執行過之後的地方,直接跳,跳哪呢?就是跳到setjmp調用時的位置,這個跳哪 的信息從哪得來的,就是setjmp的參數指向的一個BUF,你在這個BUF裏面保存了當前地址。所以,若是屢次setjmp同一個buf,則在跳到最後 一次,若是每次setjmp了不一樣的BUF,那麼哪一個BUF做爲longjmp的參數,就是跳到對應setjmp的位置。此時等同於setjmp被返回, 只不過返回值不爲0,由此判斷是longjmp過來的。繼續舉例子。
導演說,第N個鏡頭。。。而後就開始演,演了一半,穿幫了,導演說,停!此時就是longjmp,longjmp去哪?和你演的這段都不要緊。直接到當前這個鏡頭的開始位置,爲何當前鏡頭能夠拍N屢次,就是由於你在鏡頭開始位置作了一個setjmp。
如今說下sigsetjmp siglongjmp。
sigsetjmp ,siglongjmp比setjmp ,longjmp的組合多了箇中斷屏蔽信息的存儲。此時siglongjmp能夠恢復到sigsetjmp出現時的中斷屏蔽狀況。在後面給出的代碼的 test10函數中,特意作了一個setjmp ,longjmp的方式,你會發現,第二次出錯,並不會進入normal_longjmp函數。所以此時的中斷配置等信息並無對應記錄下來,屬於出錯後 的狀況。
OK。如今說說setjmp longjmp有什麼好處。
前面說了。signal對SIGSEGV這個中斷的響應函數修改後,函數退出,會再次執行MMU發生錯誤的代碼位置,所以咱們要確保函數跳過當前出錯的位置,執行到咱們但願跳過的代碼。相似C++的try catch那樣,咱們但願有try catch。以下
char *p = "1234";
TRY
p[2] = '1';//這顯然是錯的嘛
CATCH ;//
則咱們能夠
OK了,爲何這樣就能夠了呢?由於若是不是longjmp過來的,setjmp始終返回0,則此時必然會執行p[2],若是p[2]不會產生 SIGSEGV的錯誤,就不會執行longjmp,由此一切照舊,該作什麼作什麼。若是p[2] = '1'錯誤,則會發出中斷信號 SIGSEGV,而假設咱們把 下面SIGNAL_SEGV_DONE這個函數先前用 signal重載過,則此時發生的錯誤,會致使進入 SIGNAL_SEGV_DONE。以下
1 |
void SIGNAL_SEGV_DONE( int signum){ |
2 |
printf ( "SEG ERROR !\n" ); |
注意這裏第2個參數是返回的值,就是跳轉到setjmp的位置,等同於setjmp返回的值,此時等於上面if的條件不成立,則等於跳出了{}。
而若是新手仍是想不一樣,這個1怎麼就被返回到setjmp的地方,並且像函數返回同樣呢?我就說兩個事情。
一、函數的返回值是放在指定寄存器裏的,好比ARM是放在r0裏的,子函數把要return的值放在r0 裏,返回父函數,則父函數對子函數的返回值直接能夠從r0裏取得(或者不取,若是不存在返回,或者暫時不須要利用這個函數的返回值)
二、一個函數調用另外一個函數,沒什麼深奧的技巧。就是在返回時把寄存器,包括指令寄存器等等恢復成調用前的狀況。惟一是指令寄存器還要再加一下,跳過函數調用的那條指令,而後一個跳轉,就回到父函數了。
下面給出代碼,我是基於malloc_free上面進行的添加,你能夠對比第九部分的代碼差別。須要很是明確注意的如下幾點
一、這裏使用的方式,並不徹底等同C++的TRY CATCH.可是機理是同樣的。和GDB裏面的斷點中斷也是同樣的,只不事後者使用了SEGTRAP 這個中斷信號
二、signal在註冊函數時只須要一次,你別傻傻的如個人DEMO同樣,放在檢測的函數裏。
三、我這裏是爲了儘量只用頭文件,因此用了static jmp_buf SIGSEGV_buf;作成全局變量也沒有關係。
四、以下,對SIGSEGV_BEGIN 和 SIGSEGV_END的使用必定要加宏判斷。我暫時沒有想到比較好的解決方案,能把兩個宏之間的代碼描述能自動預編譯剔除掉。
2 |
SIGSEGV_BEGIN(test) //there will be one func ,static void sigsegv_done_test(int signum){ |
3 |
printf ( "you have a seciton error !\n" ); |
以上代碼,等同於
1 |
static void sigsegv_done_test( int signum){ |
2 |
if (signum != SIGSEGV){ |
6 |
printf ( "you have a section error !\n" ); |
7 |
siglongjmp(SIGSEGV_buf,1); |
test8是一個不進行中斷函數註冊的例子,你會發現什麼事情都沒有發生,和之前同樣,由於沒註冊嘛。
test9是個標準的處理方案。
test10,說過了,讓你區分longjmp,setjmp 和siglongjmp sigsetjmp的區別。
你能夠經過執行
bin/test_malloc_free_main 10
的方式調用test10,其餘雷同,以下是代碼清單。函數
malloc_free.h源碼分析
01 |
#ifndef _malloc_free_H_ |
02 |
#define _malloc_free_H_ |
04 |
#define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change |
05 |
#define ALLOC_PAGE_MASK (ALLOC_PAGE_SIZE-1) |
06 |
#define PAGE_SIZE_ALIGN(n) ((n) + ALLOC_PAGE_MASK) & (~(ALLOC_PAGE_MASK)) |
07 |
#define MALLOC_NUM_UNIT_SIZE (ALLOC_PAGE_SIZE / sizeof(void*)) |
08 |
#define MAX_MALLOC_UNIT_NUM 64 //not more than 4096*8 |
09 |
#define MAX_MALLOC_NUM (MALLOC_NUM_UNIT_SIZE * MAX_MALLOC_UNIT_NUM) // max malloc times ,you can change |
12 |
//c_malloc c_free means malloc by check,not calloc,not type cmalloc!!!!! |
13 |
#define c_malloc(a) malloc(a) |
14 |
#define c_free(a) free(a) |
15 |
#define MALLOC_FREE_INIT(...) do{}while(0) |
16 |
#define _TYPE_INDEX_MALLOC_FREE(...) |
17 |
#define _TYPE_COUNT_MALLOC_FREE(...) |
18 |
#define CHECK_PTR_RANGE(...) (1) |
19 |
#define GET_MALLOC_INDEX(...) do{}while (0) |
20 |
#define CHECK_PTR_RANGE_ER(...) do{}while(0) |
22 |
#error "if not define __MMDB_FLAG__ ,this define how to done " |
23 |
#define SIGSEGV_BEGIN(...) |
24 |
#define SIGSEGV_END(...) |
28 |
#define SIGNAL_SEGV(...) |
30 |
#define CATCH_SEGV(...) |
35 |
static jmp_buf SIGSEGV_buf; //every C file have one |
36 |
#define SIGSEGV_FUNC(name) sigsegv_done_##name |
37 |
#define SIGSEGV_BEGIN(name) static void SIGSEGV_FUNC(name)(int signum){ if (signum != SIGSEGV) {return;} |
38 |
#define SIGSEGV_END() siglongjmp(SIGSEGV_buf,1);} |
39 |
#define SIGNAL_SEGV(name) do {signal(SIGSEGV,SIGSEGV_FUNC(name));}while (0) //no return no need check |
40 |
#define TRY_SEGV() if (sigsetjmp(SIGSEGV_buf,1) == 0){ |
41 |
#define CATCH_SEGV(...) } |
42 |
#define MALLOC_FREE_INIT malloc_free_init |
43 |
#define _TYPE_COUNT_MALLOC_FREE(name) unsigned long name = 0; |
44 |
#define _TYPE_INDEX_MALLOC_FREE(name) void ** name; |
45 |
#define CHECK_PTR_RANGE(p,indexP) ((indexP[0] <= (void *)(p)) && (indexP[1] > (void*)(p))) |
46 |
#define GET_MALLOC_INDEX(p,indexP) do{indexP = get_malloc_index(p);}while (0) |
47 |
#define CHECK_PTR_RANGE_ER(p,indexP,n,NAME) do {if (CHECK_PTR_RANGE(p,indexP)){n++;}else{set_check_ptr_range_error_exit(p,indexP,n,NAME);}} while (0) |
56 |
void memory_free_init( void *); |
57 |
void **get_malloc_index( void *ptr); //not used in code ,please used GET_MALLOC_INDEX define |
58 |
void set_check_ptr_range_error_exit( void *p, void **index,unsigned long n, const char *str); //not used in code ,please used CHECK_PTR_RANGE_ER |
59 |
void *c_malloc( size_t size); |
60 |
void c_free( void *ptr); |
65 |
#endif //_malloc_free_H_ |
malloc_free.c沒有任何變化學習
一下是test_malloc_free_main.c的清單this
001 |
#include "malloc_free.h" |
004 |
typedef void (* TEST_FUNC)( void ); |
006 |
SIGSEGV_BEGIN(test) //there will be one func ,static void sigsegv_done_test(int signum){ |
007 |
printf ( "you have a seciton error !\n" ); |
011 |
static void test8( void ){ |
012 |
//test write to zero point |
014 |
// SIGNAL_SEGV(test); |
019 |
static void test9( void ){ |
020 |
//test write to zero point |
031 |
void normal_longjmp( int signum){ |
032 |
printf ( "there is normal_longjmp func!\n" ); |
035 |
static void test10( void ){ |
039 |
signal (SIGSEGV,normal_longjmp); |
041 |
if ( setjmp (buf1) == 0){ |
043 |
} else { // section error ,jmp from normal_longjmp func |
044 |
printf ( "we come back from normal_longjmp func!\n" ); |
049 |
static void test0( void ){ |
053 |
p1 =( char *)c_malloc(4); //*4); |
054 |
p2 = ( char *)c_malloc(6); |
057 |
return ; //normal check |
059 |
static void test1( void ){ |
061 |
p1 = ( char *)c_malloc(5); |
062 |
p2 = ( char *)c_malloc(6); |
065 |
return ; //free lack check |
067 |
static void test2( void ){ |
069 |
p1 = ( char *)c_malloc(5); |
070 |
p3 = p2 = ( char *)c_malloc(6); |
074 |
return ; //free more check |
076 |
static void test3( void ){ |
077 |
char **pp = ( char **)c_malloc( sizeof ( char *)*MAX_MALLOC_NUM + 1); |
079 |
for (i = 0 ; i <= MAX_MALLOC_NUM ; i++){ |
080 |
pp[i] = ( char *)c_malloc(2); |
082 |
return ; //alloc more check |
084 |
static void test4( void ){ |
086 |
p1 = ( char *)c_malloc(5); |
087 |
p2 = ( char *)c_malloc(6); |
088 |
p3 = ( char *)c_malloc(6); |
092 |
return ; //free twin check |
094 |
static void test5( void ){ |
096 |
p1 = ( char *)c_malloc(5); |
097 |
p2 = ( char *)c_malloc(6); |
100 |
return ; //free shift check |
102 |
static void test6( void ){ |
104 |
p1 = ( char *)c_malloc(5); |
105 |
p2 = ( char *)c_malloc(6); |
108 |
return ; //free zero check |
111 |
static void test7( void ){ |
113 |
_TYPE_COUNT_MALLOC_FREE(ptr_count); |
114 |
_TYPE_INDEX_MALLOC_FREE(pindex); |
117 |
p1 = ( char *)c_malloc(5); |
118 |
GET_MALLOC_INDEX(p1,pindex); |