close
文章出處

以前一直用VS 2012來調試C/C++代碼,F5、F10、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"

 

然后各種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下注釋掉該句也是運行成功。現在問題就來了:


1. 為什么free(re)過后re不為NULL呢?

我一直認為將一個對象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就不會報錯,具體看下面的測試片段:

 

#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);
}

運行結果如下:

足以說明野指針的使用是不確定的,所以為了不出現bug還是遵守我上面所說的最佳實踐做法吧。

 

2. Eclipse中為什么調試不出來這個錯誤呢?

Eclipse的調試功能也十分強大,可是這里的調試卻不友善,一個是錯誤信息看不 懂,一個是出錯位置調試不出來,雖然出錯位置就在調試出來的位置的正上面,但調試的時候if (re!=NULL) free(re);這句的確是執行成功了,所以也就不會認為是這句的問題,難道程序真正的出錯位置是在Eclipse下調試出來的出錯位置的正上面嗎?額,應該不會吧。


下面不得不簡單比較下VS和Eclipse調試功能的差異:

1. 首先如果你習慣了用VS的調試,那么轉到Eclipse下可能會有些不太習慣,尤其是大家熟知的VS下的F5、F10、F11到了Eclipse下卻變成了F8、F6、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,完全可以用它來開發你想要開發的任何程序。


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 AutoPoster 的頭像
    AutoPoster

    互聯網 - 大數據

    AutoPoster 發表在 痞客邦 留言(0) 人氣()