之前一直用VS 2012來調試C/C++代碼,F五、F十、F11用起來甚是順手,前面也寫過一篇關於VS最好用的快捷鍵:Visual Studio最好用的快捷鍵(你最喜歡哪一個), 因此對於調試C/C++代碼我一直鍾情於VS。可最近下載了一個linux環境下用C++編寫的開源庫,準備進行一番研究,因爲我對gdb調試只處在初步 階段,尚未對整個項目用gdb調試過,並且gdb調試看起來也不方便,仍是VS看的直觀。爲了省懶和省時間就將代碼弄到VS中進行編譯調試,結果發現編 譯不成功,由於裏面出現了不少相似int block[2*n];這樣的變長數組。你們知道傳統C語言和C++是不支持變長數組功能的,不過在C99標準中新增的一項功能就是容許在C語言和C++ 中使用變長數組,節省了不少資源。可恨的是,微軟的編譯器跟不上時代的步伐,C++11都出來這麼久了,微軟到如今連C99還不徹底支持(不知道最新版的 VS 2013支不支持),不知道是故意而爲之仍是其它什麼緣由。既然VS不支持變長數組,我這程序就調試不了,我也不可能一個個的把它改爲定長的。後來想到用 Eclipse CDT進行調試,就下載了個完整的Eclipse CDT(沒在已有的Eclipse上安裝CDT插件而是下載了個徹底用於C/C++開發的Eclipse,由於配置插件出現了不少問題,至今還待解決)。 Eclipse中的C/C++庫支持使用的是最新版的Cygwin,最新的g++確定是支持變長數組的,這時也發現我下載的庫的原做者也是在 Eclipse CDT下開發該項目的,由於工程目錄下有.cproject和.project這兩個文件,所以認爲在Eclipse CDT下編譯調試該工程是最佳選擇。通過嘗試,編譯是經過了,但是運行時總是出現這樣一個錯誤:No source available for "ntdll!ZwWriteFile() at 0x77a4133a"。linux
而後各類google、百度都搜不到相關的信息或只有少數幾個沒什麼價值的信息。看來只能斷點調試了,發現了問題所在位置:編程
if(i!=0){
re[i]='\0';
if (re[0]!= '#'){
j++;
if (j>=from && (to==-1 || j<=to)){
if (DEBUG) fprintf(stdout,"\n%d) processing regex:: <%s> ...\n",j,re);
parse_re(nfa,re);
}
}
free(re);
}
if (DEBUG) fprintf(stdout, "\nAll RegEx processed\n");
if (re!=NULL) free(re);
//handle -m modifier
if (m_modifier && (!anchored->get_epsilon()->empty() || !anchored->get_transitions()->empty())){
non_anchored->add_transition('\n',anchored);
non_anchored->add_transition('\r',anchored);
}
// delete non_anchored, if necessary
if(non_anchored->get_epsilon()->empty() && non_anchored->get_transitions()->empty()){
nfa->get_epsilon()->remove(non_anchored);
delete non_anchored;
}else{
non_anchored->add_any(non_anchored);
}
發現每次判斷該條件語句if (m_modifier...)事後才報上面那個錯誤,因此堅信是這條語句有問題,通過一番檢查以爲這語句沒啥問題,無奈之下乾脆將兩個判斷條件所有註釋掉了,結果仍是出現問題,問題轉到註釋語句的下面,實在不清楚是啥緣由,就仔細看了下「No source available for "ntdll!ZwWriteFile() at 0x77a4133a"這 條錯誤語句,發現是和ntdll庫有關,因而就搜ntdll庫錯誤相關的資料,最終發現多是跟堆相關,可仍是沒能解決問題。最終我仍是轉到VS下面調 試,固然前提是去掉了變長數組(還好發現變長數組只出如今兩個文件的兩個函數中,直接註釋掉了),編譯成功後運行出現錯誤:數組
點Continue接着出現錯誤:編輯器
看了下錯誤信息真的是堆問題,調試下發現是這句if (re!=NULL) free(re);執行不了,再次調試發現前面re這個對象已經經過free(re)釋放了,這裏按理說re應該爲NULL了也就是不會再次 free(re)了啊,但是實際運行的確re不爲NULL所以再次free了re,至關於一塊原本已經釋放了的內存空間再次被釋放,確定會出現堆錯誤了。 將該條件語句註釋掉後,運行成功,而後在Eclipse下注釋掉該句也是運行成功。如今問題就來了:函數
我一直認爲將一個對象free事後該對象就爲NULL了,這樣就能夠經過判斷該對象是 否爲NULL來知道該對象是否爲正確的釋放了,若是沒有釋放(上面的代碼中也就是if(i!=0)沒執行)那麼在此進行釋放以免內存泄露。這個工程庫中也是這樣作的,但是經過調試卻發現不是這樣的狀況,如今我能想到的惟一解釋就是:free(re)事後re所指內存空間的確被釋放了,但re自己的值不會改變,也就是形參的值沒有改變,因此re仍是原來的值固然就不是NULL了,這樣後面的再次free也就會被執行,但re所指的內存已經被釋放因此再次 free也就失敗了。後來通過網友@hazir的解釋,知道了原來是「野指針」的問題,即:若指針p被free或者delete以後,p並無置爲NULL,讓人誤覺得p是個合法的指針,別看free和 delete的名字(尤爲是delete),它們只是把指針所指的內存給釋放掉,但並無把指針自己幹掉,此時指針成爲了「野指針」,指向的就是「垃圾」內存。知道了緣由事後,那麼之後怎麼判斷re所指的內存是否被釋放了呢?固然上面的代碼很好解決,直接在if(i!=0)後面加 else{ free(re); }也就解決了,但是其它狀況呢?爲了防止「野指針」的產生,編程最佳實踐都建議:釋放後的指針應當即將指針置爲NULL,例:測試
free( p ); if ( p != NULL ) p = NULL;
可是並非每一個野指針再次free事後都會報錯的,只要下次有另外一個指針分配內存且以這個地址爲起始地址開始分配,那麼free就不會報錯,具體看下面的測試片斷:
google
#include <stdio.h> #include <stdlib.h> void main() { int *pint1, *pint2; pint1 = (int *) malloc (12); printf("pint alloc at : %p\n", pint1); //free pint1, and pint1 do not change to NULL free(pint1); printf("after free pint, and pint is :%p\n", pint1); //pint2 alloc the same start address with pint1 pint2 = (int *) malloc (12); printf("pint2 alloc at : %p\n", pint2); //free pint1 again, not pint2, and not occur error!!! free(pint1); }
運行結果以下:spa
足以說明野指針的使用是不肯定的,因此爲了避免出現bug仍是遵照我上面所說的最佳實踐作法吧。.net
Eclipse的調試功能也十分強大,但是這裏的調試卻不友善,一個是錯誤信息看不 懂,一個是出錯位置調試不出來,雖然出錯位置就在調試出來的位置的正上面,但調試的時候if (re!=NULL) free(re);這句的確是執行成功了,因此也就不會認爲是這句的問題,難道程序真正的出錯位置是在Eclipse下調試出來的出錯位置的正上面嗎?額,應該不會吧。插件
下面不得不簡單比較下VS和Eclipse調試功能的差別:
1. 首先若是你習慣了用VS的調試,那麼轉到Eclipse下可能會有些不太習慣,尤爲是你們熟知的VS下的F五、F十、F11到了Eclipse下卻變成了F八、F六、F5,其它的也不一樣,這樣的轉變有時候真不習慣。
2. 我以爲Eclipse下調試有一點的確比VS好,就是對函數的智能提示,Eclipse下當你講鼠標放到一個自定義函數上面,會自動顯示該函數的實現,而VS下只能顯示該函數的聲明,要知道定義還得按F12跳過去。
Eclipse下:
VS下:
其它的我就很少做比較了,好比快捷鍵方面,由於對VS快捷鍵較熟,對Eclipse快捷鍵還不是很瞭解(雖然本身最熟的語言是Java,但調試Java的次數較少),因此二者快捷鍵方便的差別性我也就不太清楚了,若是清楚的麻煩告訴我。
好了,以本身親自調試的一 個小錯誤引出了這麼一個問題:Eclipse與VS,你更喜歡哪一個呢?固然有人會說,開發C/C++與C#就用VS,開發Java就用Eclipse,可 是Eclipse可不只僅是Java的編輯器,Eclipse是全能型的,能夠編譯常見的全部語言如C/C++、C#、Python、Ruby等等,若是 你鍾愛Eclipse,徹底能夠用它來開發你想要開發的任何程序。