又見懸空指標

ryfdizuo發表於2014-04-05

概念

懸空指標:顧名思義,其指向的記憶體已經被釋放,但是指標使用者並不知道,通過指標訪問了非法記憶體,結果隨機。懸空指標常發生在指標淺拷貝場景,根本原因是資訊不同步問題。


實際場景

最近對引擎做一次比較大升級,結果還是引入了一些隱晦BUG,本文著重還原一個懸空指標的場景示例程式碼如下:

class StyleMgr { 
public:
	StyleMgr(): _default(NULL), pStyleArr(NULL)	{ /* .. 其他初始化 ..*/ } 
	
	//... 析構 ...
	
	int Create(const char* file, int index) {
		Clear();
		
		_pStyleArr = new Style[];
		// ... parse from file...
	}
	
	void Clear() {
		_hashTb.removeAllObject();
		delete [] _pStyleArr;
		_pStyleArr = NULL;
	}
	
	Style* GetStyle(int id) {
		if (!_default) {
			_default = _hashTb.find(DEFAULT_CODE);
		}
		
		if (!_hashTb.has_contains(id)) {
			return _default;
		}
		return _hashTb.find(id);
	}
	
private:
	Style*		_pStyleArr;	
	HashTable 	_hashTb;	
	Style* 		_default;
};
StyleMgr內部_pStyleArr是樣式陣列,儲存所有style指標,_hashTb為樣式雜湊表為了快速查詢,_default為查詢失敗的預設樣式。_default, _hashTb都與_pStyleArr共享內部指標。

懸空指標出錯的場景:

StyleMgr smgr = new StyleMgr();
// 1.首次建立...
smgr.Create("style.dat", 0);

// 2.獲取樣式...
style1 = smgr.GetStyle(id1);
style2 = smgr.GetStyle(id2);

// 3.其他地方再次建立
smgr.Create("style.dat", 1);

// 4.懸空指標出場:
style3 = smgr.GetStyle(bad_ID);		
注意上面4段程式碼都不在一個地方

第二段程式碼呼叫GetStyle以後此時_default指向了首次Create時_pStyleArr內部的同時也是_hashTb內部的一個樣式。

第三段程式碼再次呼叫Create函式,此時Create內部呼叫Clear以後,重建了_pStyleArr_hashTb結構,此時_default仍然不為空,持有已經釋放的記憶體地址。

第四段程式碼再次呼叫GetStyle時候,如果傳入的是合法ID,結果OK!但是當傳入非法ID,將_default返回,結果就隨機了。。。

原因分析:coding的時候沒意識到Create函式會多次重入,Clear函式本意是將整個StyleMgr成員狀態重置,忘記處理_default。於是當Create、GetStyle、Create、GetStyle組合呼叫時,最後一次的GetStyle引入了懸空指標隱患,多次重入取到的預設樣式可能隨機。


後話

巨大的一個工程中,而且涉及不同地方呼叫,指望CV很難發現問題。通過gflags也沒發現問題,因為只有查詢失敗時才返回default樣式,而且返回後還不一定出錯。

這個BUG是程式碼提交十天後才發現,當時跟蹤另外一個bug,除錯的時候偶然發現_default指向內容為空。。。然後就查啊查啊查。。。

查到問題改起來十分容易啦~

指標問題沒有小問題,對待指標問題一定小心又小心。。。

- 20140405 dizuo

相關文章