大部分的c++開發者在他們的**中會廣泛的使用stl。如果你直接用stl和visusal studio 6.0,那麼你的程式將在記憶體很低的情況下極有可能崩潰掉。原因在於沒有對new操作的結果進行檢驗。更糟的是,若new操作確實失敗了,得到的反饋也沒有乙個標準可言——有的編譯器會返回空指標,而有的會丟擲異常。 總之,如果你在mfc的專案中用stl,請注意mfc有它自己的規則。這篇文章主要討論這些問題,解釋最新的visual c++編譯器的預設行為有了怎樣的改變,並概述你在使用visual c++ 6.0時必須要做出的一些修改,這樣即使在new操作失敗時你也能安全地使用stl。
背景有多少程式設計師會檢查new操作是否失敗?是否有需要經常做這樣的檢查?我見過一些龐大而複雜的c++工程,它們是用visual c++ 6.0寫的,但沒有看到一處對new的返回結果是否是null進行了檢查。請注意是對new返回null的檢查。visual c++ 6.0中,new操作失敗時的預設行為是返回乙個null指標而不是丟擲異常。visual c++ 2003中,c執行時庫(c runtime library)的new失敗時還是返回null,但標準c++庫(standard c++ library)中的new失敗時會丟擲異常。new失敗時究竟是何種行為要看linker中是標準c++庫在前面還是c執行時庫在前面。若標準c++庫在前面,則會丟擲異常;而c執行時庫在前面,則只返回null。要改寫這個行為並強制使用會拋異常的那個new,我們需要顯示的鏈結thrownew.obj。在visual c++ 2005、2008及2010中,除非顯示鏈結nothrownew.obj,否則不管是c執行時庫還是標準c++庫,都會丟擲異常。另外要注意的是,這裡描述的行為都不涉及託管**或.net框架。若原有的visual c++ 6.0風格的**沒有預料到new操作會丟出異常,將所有這些**移植到高版本編譯器後,若是其中的new會丟擲異常,那麼產生的程式極有可能會在執行時意外終止。對這點我們必須要注意.
c++標準規定,new操作符必須在失敗時丟擲異常,具體來說,這個異常得是std::bad_alloc。這只是標準而已,具體在visual c++中的情形請見下表:
版本 純c++ mfc
visual c++ 6.0
返回null
cmemoryexception
> 6.0
std::bad_alloc
cmemoryexception
可見,在mfc環境下,丟擲的異常並不是c++標準上要求的。如果你用的stl中用catch (std::bad_alloc)來處理記憶體分配失敗,那這個只能在沒有mfc的環境下才可以。visual c++ 6.0中的stl用catch (…)來處理new失敗的情況,這種寫法可以在mfc中正常工作。
返回null的new操作符
通常兩種情形下不需要檢查new返回的指標是否是null:new永遠不會失敗或new會丟擲異常。
即使你認為new永遠都不會失敗,但不檢查返回值是乙個很差的程式設計習慣。桌面應用程式一般不太可能會遭受記憶體耗盡的窘境。但一些伺服器上需要24小時執行的程式就比較有可能碰到記憶體耗盡的情況,尤其是在一台共享應用程式伺服器上。如果你不能保證你的應用程式一直是乙個位元組都不洩露的,那由記憶體產生錯誤的機率就會增加。
如果你不檢查返回的指標是否是null的原因是由於new會丟擲異常,這也情有可原。畢竟,c++標準規定new在失敗時要丟擲異常,但這不是visual c++ 6.0的預設做法,它只會返回乙個null指標。儘管之後的版本有支援c++標準,但6.0中的做法(尤其是在和stl一起使用時)會產生問題。stl中會假定new失敗時會丟擲異常,不管使用的是何種編譯器。事實上,如果new沒有表現出這種行為並由於記憶體分配失敗而得到乙個null指標,stl接下來的行為將是不可**的,而程式也有很大的可能崩潰掉。
標準模板庫
開發人員在c++開發過程中越來越依賴於stl。stl在c++模板的基礎上提供了很多類及函式。用stl有幾個好處:首先,這個庫為各種通用任務提供了乙個一致的介面;其次,這部分**被廣泛地測試過,因此可以認為它已經沒有bug了;最後,裡面的演算法也是最佳的。
為了使stl能使用,編譯器要支援c++標準。visual c++編譯器預裝了乙個stl,其他廠家的也是能使用的。
visual c++ 6.0和new操作符
當new失敗時返回null,可以認為這個行為是bug,因為它與標準不符。所有stl的實現,包括visual c++自帶的,都預期new操作符在失敗時會丟擲異常。儘管可以改變new的行為使其遇到錯誤時丟擲異常,但這會帶來更多的不規範。我們通過以下的**來說明問題:
1.
#include < string >
2.
void
foo()
3.
6.
在visual c++ 6.0中,上面的**最終會呼叫到stl中如下的函式(節選,為說明的方便多餘的**已拿掉):
01.
void
_copy(size_type _n)
02.
17.
在try語句塊中,allocator.allocate的返回值賦給區域性變數_s,而allocator.allocate會用到new。visual c++ 6.0的預設行為是:new操作符失敗時會返回null,這就會使_s的值為null。接下來一行會將_s+1的值賦給_ptr。若_s為null,_ptr最終將為0x00000001。接下來一句_refcnt(_ptr) = 0事實上返回_ptr-1(即_ptr[-1]),即其實是在對最初返回的那個null在計算。_refcnt返回乙個null指標,接下來再將0賦值給它(*null = 0),這樣就會立即產生乙個訪問衝突的錯誤。儘管這看起來似乎是乙個bug,但stl的**其實沒有問題,只是為了得到乙個正確的行為,它需要new能丟擲異常。
讓我們再看一下new失敗時丟擲異常的執行流程。首先執行allocator.allocate,這其中的new失敗後會丟擲std::bad_alloc異常,接著就進到_catch_all再試一次。如果第二次分配也失敗了,將會有另乙個std::bad_alloc異常被丟擲,這個會被一路傳播到我們的**中,最終導致std::stting物件雖然定義了卻還是空的這樣乙個狀態。
修正new操作符
01.
#include < new >
02.
#include < new.h >
03.
#pragma init_seg(lib)
04.
namespace
05.
11.
12.
class
newhandler
13.
19.
~newhandler()
20.
23.
private
:
24.
_pnh
m_old_new_handler;
25.
} g_newhandler;
26.
}
// namespace
27.
將以上**包含進我們的工程,那麼new失敗時的錯誤處理會被自動修改,例子中將會丟擲std::bad_alloc。
new(std::nothrow)丟擲錯誤
在visual studio 6.0中,如果將以上**包含進去,而分配記憶體時用new(std::nothrow),執行release時反而會報錯,顯示"abnormal program termination"。這是個比較細節性的問題,是由於編譯器的優化造成的。可以到project settings | c/c++ | general | optimizations將優化關掉以避免這個問題,或者還可以自己寫乙個new(std::nothrow)(請參考源**newnothrow.cpp)。
總結visual c++ 6.0預設提供的new操作與stl並不相容。即使前面提到了一些解決方法,仍有可能在用第三方的庫或stl中個別其他函式時會有麻煩。vc 6.0中new、new(std::nothrow)和stl的不相稱不能完全的解決掉,但如果不用上面的方法,肯定會有很到的麻煩。
mfc專案中,stl中用new的地方是否能經受異常的考驗完全取決於你用的stl中的錯誤處理時如何寫的。大多數都會用catch(…)而不是catch(std::bad_alloc),但這並不是必須的。
最後,正如最開始所提到的,visual c++ 2005到2010都已修正了這些問題。
例項:
引用:
SYN 攻擊原理及解決方法
原理 syn foold攻擊主要針對tcp通訊三次握手期間做的手腳,所以要弄懂這個攻擊的原理我們首先必須知道tcp三次握手的詳細過程 由上圖可知tcp三次握手顧名思義要經過三個步驟,這三個步驟分別是 客戶端向服務端傳送syn j 同步訊號假設序號為j 相當於通知服務端我要開始建立連線了 服務端收到客...
ubuntu軟體中心崩潰解決方法
ubuntu 下面問題記錄 一 安裝好來ubuntu 桌面系統後首次使用正常,但是關機以後第二次進入後,點選軟體中心時,顯示軟體中心崩潰。網上找了下別人的解決方法 1 提示說是lists出錯 我的正是這種情況 使用如下命令可以修復 1 刪除lists sudo rm var lib apt list...
死鎖及解決方法
死鎖的概念 quad 死鎖 指的是 quad 多個執行緒各自占有一些共享資源,並且互相等待其他執行緒占有的資源才能進行,而導致兩個或者多個執行緒都在等待對方釋放資源,都停止執行的情形。quad 因此,某乙個同步塊需要同時擁有 兩個以上物件的鎖 時,就可能會發生 死鎖 的問題。下面案例中,化妝執行緒 ...