程式設計師必須了解的記憶體知識

2021-07-08 21:02:55 字數 4559 閱讀 9406

在c和c++語言開發中,指標、記憶體一直是學習的重點。因為c語言作為一種偏底層的中低階語言,提供了大量的記憶體直接操作的方法,這一方面使程式的靈活度最大化,同時也為bug埋下很多隱患。

因此,無論如何,我們都要對記憶體有乙個清晰的理解。

1. 對記憶體的分配

32位作業系統支援4gb記憶體的連續訪問,但通常把記憶體分為兩個2gb的空間,每個程序在執行時最大可以使用2gb的私有記憶體(0x00000000—0x7fffffff)。即理論上支援如下的大陣列:

1

charszbuffer[2*1024*1024*1024];

當然,由於在實際執行時,程式還有**段、臨時變數段、動態記憶體申請等,實際上是不可能用到上述那麼大的陣列的。至於高階的2gb記憶體位址(0x80000000—0xffffffff),作業系統一般內部保留使用,即供作業系統核心**使用。在windows和linux平台上,一些動態鏈結庫(windows的dll,linux的so)以及ocx控制項等,由於是跨程序服務的,因此一般也在高2gb記憶體空間執行。

可以看到,每個程序都能看到自己的2gb記憶體以及系統的2gb記憶體,但是不同程序之間是無法彼此看到對方的。當然,作業系統在底層做了很多任務作,比如磁碟上的虛擬記憶體交換(請看下以標題),不同的記憶體塊動態對映等等。

2. 虛擬記憶體

虛擬記憶體的基本思想是:用廉價但緩慢的磁碟來擴充快速卻昂貴的記憶體。在一定時刻,程式實際需要使用的虛擬記憶體區段的內容就被載入物理記憶體中。當物理記憶體中的資料有一段時間未被使用,它們就可能被轉移到硬碟中,節省下來的物理記憶體空間用於載入需要使用的其他資料。

在程序執行過程中,作業系統負責具體細節,使每個程序都以為自己擁有整個位址空間的獨家訪問權。這個幻覺是通過「虛擬記憶體」實現的。所有程序共享機器的物理記憶體,當記憶體使用完時就用磁碟儲存資料。在程序執行時,資料在磁碟和記憶體之間來回移動。記憶體管理硬體負責把虛擬位址翻譯為實體地址,並讓乙個程序始終執行於系統的真正記憶體中,應用程式設計師只看到虛擬位址,並不知道自己的程序在磁碟與記憶體之間來回切換。

從潛在的可能性上說,與程序有關的所有記憶體都將被系統所使用,如果該程序可能不會馬上執行(可能它的優先順序低,也可能是它處於睡眠狀態),作業系統可以暫時取回所有分配給它的物理記憶體資源,將該程序的所有相關資訊都備份到磁碟上。

程序只能操作位於物理記憶體中的頁面。當程序引用乙個不在物理記憶體中的頁面時,mmu就會產生乙個頁錯誤。記憶體對此事做出響應,並判斷該引用是否有效。如果無效,核心向程序發出乙個「segmentation violation(段違規)」的訊號,核心從磁碟取回該頁,換入記憶體中,一旦頁面進入記憶體,程序便被解鎖,可以重新執行——程序本身並不知道它曾經因為頁面換入事件等待了一會。

3. 記憶體的使用

對於程式設計師,我們最重要的是能理解不同程序間私有記憶體空間的含義。c和c++的編譯器把私有記憶體分為3塊:基棧、浮動棧和堆。

我們來看乙個例子:

1

2

3

4

5

6

7

constintn = 100;

voidfunc(void)

這個函式如果執行,其中n由於是全域性靜態變數,位於基棧,i和pbuff這兩個函式內部變數,i位於浮動棧,而pbuff指向的由malloc分配的記憶體區,則位於堆疊。

在記憶體理解上,最著名的例子就是執行緒啟動時的引數傳遞。

函式啟動乙個執行緒,很多時候需要向執行緒傳引數,但是執行緒是非同步啟動的,即很可能啟動函式已經退出了,而執行緒函式都還沒有正式開始執行,因此,絕不能用啟動函式的內部變數給執行緒傳參。道理很簡單,函式的內部變數在浮動棧,但函式退出時,浮動棧自動拆除,記憶體空間已經被釋放了。當執行緒啟動時,按照給的引數指標去查詢變數,實際上是在讀一塊無效的記憶體區域,程式會因此而崩潰。

那怎麼辦呢?我們應該直接用malloc函式給需要傳遞的引數分配一塊記憶體區域,將指標傳入執行緒,執行緒收到後使用,最後執行緒退出時,free釋放。

我們來看例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

//這個結構體就是參數列

typedefstruct_clisten_listenaccepttask_param_

sclistenaccepttaskparam;

//習慣性寫法,設定結構體後,立即宣告結構體的尺寸,為後續malloc提供方便

constulongsclistenaccepttaskparamsize=sizeof(sclistenaccepttaskparam);

//這裡接收到連線請求,申請引數區域,將關鍵資訊帶入引數區域,幫助後續執行緒工作。

boolclisten::listentaskcallback(void* pcallparam,int& nstatus)

//這是執行緒函式,負責處理上文accept到的socket

boolclisten::listenaccepttask(void* pcallparam,int& nstatus)

4. 記憶體bug

無規則的濫用記憶體和指標會導致大量的bug,程式設計師應該對記憶體的使用保持高度的敏感性和警惕性,謹慎地使用記憶體資源。使用記憶體時最容易出現的bug是:

1. 壞指標值錯誤:在指標賦值之前就用它來引用記憶體,或者向庫函式傳送乙個壞指標,第三種可能導致壞指標的原因是對指標進行釋放之後再訪問它的內容。可以修改free語句,在指標釋放之後再將它置為空值。

1

free(p); p = null;

這樣,如果在指標釋放之後繼續使用該指標,至少程式能在終止之前進行資訊轉儲。

2. 改寫(overwrite)錯誤:越過陣列邊界寫入資料,在動態分配的記憶體兩端之外寫入資料,或改寫一些堆管理資料結構(在動態分配記憶體之前的區域寫入資料就很容易發生這種情況)

1

p =malloc(256); p[-1] = 0; p[256] = 0;

3. 指標釋放引起的錯誤:釋放同乙個記憶體塊兩次,或釋放一塊未曾使用malloc分配的記憶體,或釋放仍在使用中的記憶體,或釋放乙個無效的指標。乙個極為常見的與釋放記憶體有關的錯誤就是在for(p=start;p=p->next)這樣的迴圈中迭代乙個鍊錶,並在迴圈體內使用free(p)語句。這樣,在下一次迴圈迭代時,程式就會對已經釋放的指標進行解除引用操作,從而導致不可預料的結果。

我們可以這樣迭代:

1

2

3

4

5

6

structnode *p, *tart, *temp;

for(p = start; p ; p = temp)

關於程式設計師的記憶體知識,就談到這裡。

程式設計師必須了解的記憶體知識

c和c 語言開發中,指標 記憶體一直是學習的重點。因為c語言作為一種偏底層的中低階語言,提供了大量的記憶體直接操作的方法,這一方面使程式的靈活度最大化,同時也為bug埋下很多隱患。因此,無論如何,我們都要對記憶體有乙個清晰的理解。1.對記憶體的分配 32位作業系統支援4gb記憶體的連續訪問,但通常把...

程式設計師必須了解的網路基礎知識的 下

67 直通電纜的標準顏色順序是什麼?橙色 白色,橙色,綠色 白色,藍色,藍色 白色,綠色,棕色 白色,棕色。68 什麼協議落在 tcp ip 協議棧的應用層之下?以下是 tcp ip 應用層協議 ftp,tftp,telnet 和 smtp。69 您需要連線兩台電腦進行檔案共享。是否可以這樣做,而不...

Java程式設計師必須了解的開源協議

mpl license bsd 鼓勵 共享,但需要尊重 作者的著作權。bsd由於允許使用者修改和重新發布 也允許使用或在bsd 上開發商業軟體發布和銷售,因此是對商業整合很友好的協議。而很多的公司企業在選用開源產品的時候都首選bsd協議,因為可以完全控制這些第三方的 在必要的時候可以修改或者二次開發...