記憶體的問題是c++的重要問題,解決c++使用過程中的記憶體問題,就可以解決使用記憶體異常(bug)和記憶體洩露(無法抵制壓力測試)。記憶體問題難,關鍵在於我們對於記憶體有太多的未知數。規範的意義,在於把程式執行時記憶體的不確定性變為可知的、可確定性,這樣就可以達到有效避免錯誤。
所有的資料(資料段)和執行指令(程式段)都放在記憶體中。下面解釋一下,我理解的程式的執行過程(僅作參考,因為我還不熟悉vc的編譯和微軟作業系統的管理機制,正確性未知),以幫助大家了解記憶體問題。
編譯過程:編譯過程是將程式設計師寫的c++程式生成計算機可識別的二進位制**段,它是一段**,而且還是邏輯的**段。因為,程式裡的位址都是相對的,是以0作為首位址的。在裝入記憶體的時候,計算機開闢出跟**段一樣大的一塊記憶體,存放**,這段記憶體叫做棧,並且os記錄記憶體段的首位址。用於計算資料和指令的實際記憶體位址。其結構可用下面的邏輯圖來表示:
程式段函式:main
int a (邏輯位址為0x000032,實體地址就是這個邏輯位址+程式段開頭);
0x0002568
在這個**段中,存放著類定義,函式定義和實現,全域性變數等。
類定義說明了類中資料成員、型別和函式列表,寒暑列表指向了乙個個的成員函式。可理解為下圖
函式定義就是乙個可執行的**段,結構簡單:用彙編來表示如下
:push 記錄當前執行狀態的ip等
: push
: push
:move:向函式裡傳遞引數
:move
…..執行**
pop:取出函式執行前的執行狀態,然後跳過去
pop:
接著往下執行。
下面說明變數的定義,這個很簡單,關鍵是為變數分配記憶體而已。
比如:int a;
d40個位元組
b8個位元組
a4個位元組
float b,
int d[10];
裝到記憶體後,就是
四個位元組:a
八個位元組:b
10*4個位元組 d:
我發現資料在記憶體中都是倒著的。
由於靜態陣列的分配是在程式裝載時進行的,所以程式陣列的個數不能定義為變數,因為變數是在執行時,才知道其值的;因此,大家在定義變數的靜態陣列時,是無法編譯通過的。
執行過程:
乙個程式要想得到執行,首先要由os裝入記憶體,然後就是os系統找到程式(exe)的入口函式執行位址main,這個函式在c裡很直觀,在vc中發生了變異,且被隱藏了起來。從管理角度,這個函式,實現了管理員的功能,它把乙個個的類實現的任務,組織起來,完成使用者的要求。那就些具體類就是我們這些開發人員,各司其職,完成自己能力可以完成的任務。
如果說exe的執行主要是由os負責的,那麼dll的執行則是由exe負責的。應用程式通過裝入dll(包括類定義等),執行dll中的全域性變數構造,然後就把dll準備好了,供exe呼叫,我們呼叫的輸出函式,實際上就相當於exe中的main,只不過exe只有乙個入口,而dll可以有多個入口而已。
由上面可以看到,程式在記憶體中大小是固定的。那麼動態分配又是怎麼一回事呢?以上的程式段,只有定義和靜態資料的分配,他們都是在棧中進行的。程式執行起來之後,比如要分配乙個類物件,那麼作業系統就會到上面的**中查詢類定義,有什麼函式,有什麼成員變數,然後根據成員變數的大小在堆裡分配記憶體,並將其記憶體其實位址的值賦值為函式列表位址。
棧: 堆:
以上的說明,基本描述了程式的執行過程。下面專門描述記憶體錯誤出現的時機並對其原因進行解釋。
1:在模組之間傳遞類的指標時,如果兩個類不完全一致,如果剛好在a中使用了a中具有但b中沒有的函式,這個指標又是由b傳給a的,那麼就會出現錯誤。由於兩個類位於不同的模組中,並當作兩個不同的類定義,當用a的規則來對b解碼時,如果定義不一致,肯定會出現問題,而編譯器卻無法發現這個問題,從而留下了問題的隱患。這個我們制定長報文傳輸服務標準是一脈相承的,目的是編碼端和解碼端按照統一的規則解碼。
2.由於記憶體對所有的程式都是公開的,屬於全民所有制的社會主義體制。不同人編寫的**,函式往往存在一定的差異,這是不可消除的。也許這就是自描述語言xml產生的乙個理由吧,它通過自描述的方式,消除了格式化語言的弊端。鑑於這種情況,我們對於記憶體的情況就處於一種未知狀態,比如乙個記憶體分配後,是否是空,函式的執行結果是否對記憶體進行了賦值操作。等等著一些,我們都是不知道的。但是,我們清楚我們自己的行為,我們知道自己要幹什麼。我們不能假定,別人如何幹,我們必須自己動手來這樣幹。
a:在分配記憶體之前對指標進行初始化:這主要是因為我們在呼叫分配後,當分配不成功時,是不是會為我們=null;如果沒有為我們賦空,而此時指標又指向乙個無效的記憶體,那麼問題立刻就出來了。這一點特別應用在通過函式返回指標之前。
b:在呼叫分配之後,立刻對指標進行判斷,然後才可以使用,這一點比較簡單,因為我們不知道記憶體分配是否成功,我們不能假定一定能成功。
c:所謂的異常,是由於使用了無效的記憶體,那麼有效的記憶體一直得不到釋放,就形成了記憶體的洩漏,記憶體的洩漏可能不會立刻影響你程式的執行,但是它經不起壓力的測試,有人說:window作業系統支援虛擬記憶體,就是把硬碟的一部分當作記憶體使用,因而永遠都不會出現記憶體不夠用,但是,我發現好像不完全是這樣,虛擬記憶體的大小也是受硬碟所限的,即使這些不考慮,硬碟的訪問速度和記憶體的訪問速度差別有多大,不言而喻。
洩漏出現的可能性很大:
1.1 是由於根本沒有delete
1.2 是由於邏輯的問題,沒***在所有的邏輯路徑下都能執行到delete
1.3 沒有加,如果分配的是乙個陣列,如下
double *p=null;
p = new double [10];
那麼刪除的時候,也要以陣列的條件刪除;
如下:delete p;//正確
如果delete p;
那麼就只刪除了p的第乙個位址,洩漏了9*sizeof(double)個位元組。
對於二維,甚至更高維要迴圈刪除。
1. 4為避免野指標(指向無效記憶體的指標),我們在呼叫釋放後,應立即=null。
d:記憶體分為兩種,一種是沒有管理器的,一種是有管理器的(一般內部通過鍊錶來管理資料),
沒有管理器的俗稱沒人管單身,占有大多數,一般情況下是這樣的,這些記憶體的釋放需要自己動手來完成。
有管理器的,佔很少的一部分,一般情況下,你分配的記憶體都屬於一類,而且記憶體快數量多,為了管理方便,就提供了管理器,由管理器來統一管理。由於他們屬於同一類,如item,管理器是知道記憶體的結構的,因此它有管理的能力。bcg裡有好多類都實現了管理。對於大家採用的carray<*,*〉clist<*,*>這些情況下,你還是自己來管理吧,因為carray不知道指標的記憶體結構,它沒有能力合理的刪除記憶體呀。正所謂巧婦難為無公尺之璀;如果大家實在不知道是否要刪除,那麼你可以刪除以下,如果出錯了,那肯定是有人幫你刪了。恭喜你:)
e:bug本來是除錯(debug)版本軟體針對使用邏輯錯誤的提示資訊。通過bug查詢使用邏輯錯誤,一般認為,邏輯正確之後,就不會出現「傳入引數=null,無效,不是視窗等等」,assert也就彈不出bug了。這種bug,在程式的內部,進行了assert而沒有進行throw所以我們的try根本抓不住他,只有那種,不是邏輯錯誤的,我們無法通過除錯來發現的錯誤,才會throw,那樣,我們才能抓住他try ..catch…
鑑於,邏輯錯誤除錯的困難,而且,我們的軟體也不能成為release.那麼我們沒有辦法,我們只能將assert檢查的內容拿到函式的外面,讓我們先進行檢查,以逃過assert 的追剿。
f:對於某些函式和類中定義的操作符,一般都有assert.有時,我們不知道裡面的assert檢查了什麼內容,那麼就請你來除錯看看吧,這樣,你就知道該檢查什麼了!!只要你做夠熟練,你直接可以通過assert對話方塊來判斷錯誤的**。那麼你就是除錯高手了。congretulation on your successs!
g:請記住我的一句話,棧裡面的記憶體是自動消亡的,堆裡面的記憶體是永遠不會自己消亡的。所以請大家要學會了解那些記憶體是在堆裡面的,如果沒有管理器來替你維護,那麼你就要自己操作了。
h:還有一種錯誤,也可以歸類到記憶體錯誤裡面,就是函式返回只在函式裡有效地記憶體資料,這些內存在函式返回之後,就會被釋放,那麼外部的變數,就無法使用這段記憶體了,為什麼呢,因為返回的記憶體是在棧中,它在函式返回後,就無效了,所以要想使函式返回後記憶體有效,這段記憶體必須是在堆裡的。這就是函式返回記憶體的原則。
i:當我們都知道釋放記憶體的時候,我們還需要保證,我們是否釋放乾淨了記憶體,這也是很重要的一面,經驗證明,我們經常會犯這種錯誤。
j:為了合理管理記憶體,乙個簡單的原則就是誰分配,誰釋放,尤其在多個類的情況下,那各類分配的,就有哪個類來負責釋放,正是各司所職的道理,用記憶體傳遞除外。
k:有時候,我們採用訊息傳遞記憶體資料:如果我們傳遞的是指標(記憶體),那麼就要通過sendmessage保證接收訊息方能夠讀取完記憶體後,傳送方才可以釋放記憶體,如果不是記憶體指標,可以使用postmessage.其實,有乙個訊息,為我們考慮了這些那就是wm_copydata 它會在剪下版分配乙個臨時記憶體,用來保證資料傳輸的正確性。
l:執行緒管理:執行緒管理造成的記憶體洩露往往會被大家忽視,因為大家以為執行緒執行完畢後,就會自己釋放資源,由於記憶體洩露的隱蔽性,我也一直這樣認為,直到前不久關注多核時代到來引起並行開發熱潮時才發現這個問題。執行緒執行完成後,但是執行緒的資源還沒有釋放!
如果不釋放,你看看等你開闢了很多執行緒後,程式執行有多慢:
這就要求我們在判斷執行緒執行完畢後,要釋放執行緒獨有資源。
getexitcodethread( phandle->m_threadhandle, &dwexitcode )
//獲得執行緒phandle的當前狀態
if ( dwexitcode != still_active )
//如果已完成綜上所述:記憶體問題極其複雜,需要每個人去不斷積累經驗,共同學習,共同進步。
關於記憶體洩漏和記憶體溢位的問題
很早就想寫這篇部落格,一直沒有時間,開篇一句話概括兩者的關係 記憶體洩漏導致記憶體溢位 那就先說一下記憶體洩漏吧,某乙個位置的記憶體洩漏,或者說導致一次記憶體洩漏沒有什麼大的影響,但是累積起來多了,那就造成了oom記憶體溢位的錯誤了,那麼什麼是記憶體洩漏呢換句話說什麼能導致記憶體洩漏呢,怎樣避免記憶...
關於堆記憶體和棧記憶體釋放
js 中的記憶體分為堆記憶體和 棧記憶體 堆記憶體 儲存引用型別值 物件 鍵值對 函式 字串 棧記憶體 提供js 執行的環境和儲存基本型別值 堆記憶體釋放 讓所有引用堆記憶體空間位址的變數賦值給null 即可 沒有變數占用這個堆記憶體了 瀏覽器會在空間的時候把它釋放掉 棧記憶體釋放 一般情況下,當函...
tensorflow gpu記憶體使用問題
tensorflow對於gpu的使用是先佔住所有可用資源,然後使用某塊gpu進行計算,因此在 中需要對gpu的使用做一定的限定根據自己的需求。當你不想讓tensorflow占用你所有的gpu時,可限定tensorflow的可見gpu cuda visible devices 1 設定 1 為可見gp...