從記憶體角度看問題

2021-04-15 12:46:23 字數 2390 閱讀 7524

本人程式設計時間雖然不長。但現在覺得一些問題不能光從語法角度理解,能從在記憶體中是如何的角度考慮,似乎問題想得更清楚,更容易理解。畢竟我們讓乙個應用程式執行,就是啟動乙個程序,作業系統賦予它位址空間,將相應的**和資料載入到位址空間。再載入dll,啟動新執行緒,也都是在分配記憶體(物理空間)載入**,或維護一些資訊。

首先,程式設計的人通常都遇到過的,如訪問衝突(testmemory.exe 中的 0x004169bd 處未處理的異常: 0xc0000005: 寫入位置 0x00000000 時發生訪問衝突),堆疊破壞(run-time check failure #2 - stack around the variable 'narrtest' was corrupted.)。要出現這樣的錯誤,最少用兩行**就可以了,而且有多種方式。但如果我們是負責乙個大型程式的一部分,修改別人的程式,或者自己的程式與別人的程式有某種方式的互動,有時候就不好查出錯誤在哪。但只要出現了就說明我們操作了非我們定義或宣告要使用的記憶體。那麼先在區域性範圍檢查寫記憶體的地方(賦值,拷貝等處),然後再檢查一些成員變數,全域性變數的寫入,呼叫的類中成員函式的使用。

不過,我覺得最好的方法來避免這些問題,還是養成一些好的習慣。比如說用巨集,避免修改陣列長度後,沒有把程式中所有相應位置的都改過來;再有如使用指標前,檢查是否為空,如函式開始處檢查傳進來的引數;與別的程式互動,或較大程式中,公用的一些巨集放入乙個檔案中,等等。

這其中,也要注意編譯器公升級後,有些資料型別長度變化。我就遇到過程式用2005編譯後,出現了問題,是因為有個型別位數擴充套件了一倍。

其次,以前看見介紹強制型別轉換時,總看見有介紹各種轉換的語法規則。但是我覺得,一切規則恐怕都源於,各種型別的資料是怎樣的乙個記憶體結構,還有就是未賦值的記憶體不一定是0。如果兩個資料型別的所用的記憶體大小不同,那麼轉換時,就必然要考慮了。乙個原本用四位元組存放的資料能放到乙個兩位元組的資料中嗎?除非那四個位元組沒有都使用,前面的兩個位元組都是0。那麼兩個位元組的資料轉換到四個位元組的資料就一定沒問題了嗎?還要看多出的那兩個位元組,編譯器預設是怎麼處理的。

當然四個字資料節對四個位元組位元組資料,也不一定就合理,特別是資料型別之間的指標轉換,因為可能會改變了編譯器對指標所指向的記憶體單元的解釋方式,如:

float ftest = 1234.56;

int *pntest = (int*)&ftest;

int ntest = 100;

float *pdwtest = (float*) &ntest;

如果是值轉換,考慮兩者範圍是否一樣,符號是否一樣,當然這些編譯器會給出警告。就看你注意不注意。

這一點不只對簡單資料型別是如此的,對於複雜資料型別也是如此的。如基類和派生類之間的轉換。定義的類物件在記憶體中就是由其成員變數組成,派生模擬基類多定義了成員物件,則在記憶體中也多佔了記憶體,而且基類的成員變數是在派生類成員變數之前的(在記憶體中)。所以基類指標可以指向派生類物件,因為記憶體中確實有一塊可以認為是基類物件的記憶體,但派生類指標指向基類指標則不同了,記憶體裡是一塊合理的基類物件,和一塊未知的記憶體。指標之間強制轉換可以,但最好不是讓編譯器隱式轉換(如:函式引數處),而用顯式轉換,並明確此後使用的是合法的記憶體。

sub mysub;//派生類

super mysuper = mysub;//基類

mysuper.somemethod();//該函式是乙個虛函式

這段**除錯時的監檢視在上面已經給出,可以看出這時兩個物件中虛函式表不同了。因為定義的super是乙個物件,不是指標,那麼會分配新的記憶體,這塊記憶體是按super的記憶體結構分配(虛函式表也按super型別填充),而此後再用賦值或複製建構函式時,都不會改變虛函式表中內容,只會改變顯式宣告的成員變數。因此這時它只會呼叫基類的函式。

再有,就是執行緒同步的問題了。當然執行緒同步的方式有很多。但為什麼要做同步呢,因為同屬乙個程序的執行緒共享程序的位址空間。如果我們有乙個合理的記憶體位址就可以訪問該記憶體,一塊記憶體被兩個執行緒同時寫,或乙個執行緒想知道某個特定時候某塊記憶體的值,則需要執行緒同步了。

還有就是核心物件了,因為在程式執行過程中,就是cpu和內存在起主要作用,因此程序,執行緒,執行緒同步的一些物件等這些物件,就只能在記憶體中開出一部分來維護他們的主要資訊。而通常這些物件都是很關鍵的東西,如果允許使用者隨意訪問,作業系統就很難管理了,所以這些物件的修改都要用系統提供的一些函式,而不能說獲得乙個指向他們的指標就任意修改.

近日做了乙個測試,主要測試函式的呼叫速度。乙個測試是在乙個類的某個函式內呼叫這個類的另乙個函式,另乙個測試是在乙個應用程式內動態載入乙個dll,獲得這個dll的匯出函式位址後,呼叫dll裡的函式。這兩個測試,都是執行該函式10000次後取平均時間,都用多**計時器保證精度。結果發現這兩者的速度差別很小。後來想了想,也對。dll只要load後就在程序的位址空間裡,獲得了其匯出函式的位址也就明確了其在程序位址空間的位址,那和第乙個測試一樣了,都是執行程序位址空間某處位址上的**。如果說耗時間的話只在load動態庫時耗時間。對於靜態載入動態庫,也一樣,只不過靜態載入是在應用程式剛開始執行時載入,並通過在dll的輸出節中找到輸出函式的位址。

架構之路 從管理者的角度看問題

同步發布在知乎,也不知道在裡這算不算水文,能不能上首頁。但園子裡還有一千多粉絲,我主要是想通知下面這件事 這個系列寫得很坎坷啊!實在是沒時間。本來是計畫一周一篇的,這都多少周了?野生程式設計師 優先招聘 裡,這大話都說出去了,打臉不能打得太狠啊。有好些同學想跟我混,但我目前確實沒這個實力 當然,也不...

架構之路 從管理者的角度看問題

同步發布在知乎,也不知道在裡這算不算水文,能不能上首頁。但園子裡還有一千多粉絲,我主要是想通知下面這件事 這個系列寫得很坎坷啊!實在是沒時間。本來是計畫一周一篇的,這都多少周了?野生程式設計師 優先招聘 裡,這大話都說出去了,打臉不能打得太狠啊。有好些同學想跟我混,但我目前確實沒這個實力 當然,也不...

從產品角度看問題 俞軍十二條軍規

1 pm首先是使用者。2 站在使用者角度看待問題。3 使用者體驗是乙個完整的過程。4 追求效果,不做沒用的東西。5 發現需求,而不是創造需求。6 決定不做什麼,往往比決定做什麼更重要。7 使用者是很難被教育的,要迎合使用者,而不是改變使用者。8 關注最大多數使用者,在關鍵點上超越競爭對手,快速上線,...