一.虛擬地址空間
64位的CPU一次處理64Bit(8字節)數據。
32位編譯模式
在32位模式下,一個指針或地址占用4個字節的內存,共有32位,理論上能夠訪問的虛擬內存空間大小為 2^32 = 0X100000000 Bytes,即4GB,有效虛擬地址范圍是 0 ~ 0XFFFFFFFF。
程序能夠使用的最大內存為 4GB,跟物理內存沒有關系。
如果程序需要的內存大於物理內存,或者內存中剩余的空間不足以容納當前程序,那么操作系統會將內存中暫時用不到的一部分數據寫入到磁盤,等需要的時候再讀取回來,而我們的程序只管使用 4GB 的內存,不用關心硬件資源夠不夠。
如果物理內存大於 4GB,例如目前很多PC機都配備了8GB的內存,那么程序也無能為力,它只能夠使用其中的 4GB。
64位編譯模式
在64位編譯模式:能夠訪問的虛擬內存空間大小為 2^64。這是一個很大的值,幾乎是無限的,就目前的技術來講,不但物理內存不可能達到這么大,CPU的尋址能力也沒有這么大,實現64位長的虛擬地址只會增加系統的復雜度和地址轉換的成本,帶不來任何好處,所以 Windows 和 Linux 都對虛擬地址進行了限制,僅使用虛擬地址的低48位(6個字節),總的虛擬地址空間大小為 2^48 = 256TB。
需要注意的是:
32位的操作系統只能運行32位的程序(也即以32位模式編譯的程序),64位操作系統可以同時運行32位的程序(為了向前兼容,保留已有的大量的32位應用程序)和64位的程序(也即以64位模式編譯的程序)。
64位的CPU運行64位的程序才能發揮它的最大性能,運行32位的程序會白白浪費一部分資源。
二,內存分配
Windows:在默認情況下會將高地址的 2GB 空間分配給內核(也可以配置為1GB)。
Linux: 默認情況下會將高地址的 1GB 空間分配給內核。
內核空間
分配給內核的這段空間成為內核空間。
為了安全,只能借助系統API來訪問你自己,這個API函數俗稱為 System Call
用戶空間
應用程序只能使用剩下的 2GB 或 3GB 的地址空間,稱為用戶空間(User Space)。
注意:為神馬內核空間和用戶空間要放到一個地址空間中,單獨給內核空間一段地址空間不行么?。
內核用有自己獨立的地址空間讓內核處於一個獨立的進程中,
這樣每次進行系統調用都需要切換進程。切換進程的消耗是巨大的,不僅需要寄存器進棧出棧,還會使CPU中的數據緩存失效、MMU中的頁表緩存失效,這將導致內存的訪問在一段時間內相當低效。
而讓內核和用戶程序共享地址空間,發生系統調用時進行的是模式切換,模式切換僅僅需要寄存器進棧出棧,不會導致緩存失效;現代CPU也都提供了快速進出內核模式的指令,與進程切換比起來,效率大大提高了。
MMU:內存管理單元,負責虛擬地址映射為物理地址。
虛擬地址:現代操作系統都使用分頁機制來管理內存,這使得每個程序都擁有自己的地址空間。每當程序使用虛擬地址進行讀寫時,都必須轉換為實際的物理地址,才能真正在內存條上定位數據。
1,內存分配的類型:
在C/C++中內存分為5個區,分別為棧區、堆區、全局/靜態存儲區、常量存儲區、代碼區。
靜態內存分配:編譯時分配。包括:全局、靜態全局、靜態局部三種變量。
動態內存分配:運行時分配。包括:棧(stack): 局部變量。堆(heap): c語言中用到的變量被動態的分配在內存中。(malloc或calloc、realloc、free函數)
這是linux32位環境下的內存分布情況
2.變量的內存分配:
棧區(stack):指那些由編譯器在需要的時候分配,不需要時自動清除的變量所在的儲存區,如函數執行時,函數的形參以及函數內的局部變量分配在棧區,函數運行結束后,形參和局部變量去棧(自動釋放)。棧內存分配運算內置與處理器的指令集中,效率高但是分配的內存空間有限。
堆區(heap):指哪些由程序員手動分配釋放的儲存區,如果程序員不釋放這塊內存,內存將一直被占用,直到程序運行結束由系統自動收回,c語言中使用malloc,free申請和釋放空間。
全局數據區(global data):全局變量和靜態變量的儲存是放在一塊的,其中初始化的全局變量和靜態變量在一個區域,這塊空間當程序運行結束后由系統釋放。
常量儲存區(const):常量字符串就是儲存在這里的,如“ABC”字符串就儲存在常量區,儲存在常量區的只讀不可寫。const修飾的全局變量也儲存在常量區,const修飾的局部變量依然在棧上。
程序代碼區:存放源程序的二進制代碼。
三.堆與棧
棧:
先進后出原則
一段連續的內存,需要同時記錄棧頂和棧底,才能對當前的棧定位。
內存有限,一般是1M-8M,超過這個值就會**棧溢出**。
棧通常也叫堆棧, 但是這里面的堆依然是堆,堆棧這個詞並不包含誰。
堆和棧區別->c語言的角度分析:
1、存儲方式:
棧:在函數調用時,棧中存放的是函數中各個參數(局部變量)。棧底下是函數調用后的下一條指令。
堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
具體請看例子:
void function()
{
int *p = (int *)malloc(10*sizeof(int));
}
2、管理方式:
棧:由系統自動分配空間,同時系統自動釋放空間。例如,聲明在函數中一個局部變量“int b“。系統自動在棧中為b開辟空間,當對應的生存周期結束后棧空間自動釋放。
堆:需要程序員手動申請並且手動釋放,並指明大小。在C語言中malloc函數申請,釋放free函數,在C++中new和delete實現。
3、空間大小不同:
棧:一般情況下是1-8M大小的內存,超過就會棧溢出。
堆:獲得空間根據系統的有效虛擬內存有關,比較靈活,比較大。
3.回收內存是否產生碎片問題:
棧:空間連續的,所以不會產生碎片。
堆:鏈式存儲,會產生碎片。
5、數據擴展方式:
棧:向低地址擴展的數據結構,是一塊連續的內存的區域。
堆:向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。
簡單補一下高低地址的問題:
如int a=16777220,化為十六進制是0x01 00 00 04則04屬於低字節,01屬於高字節(共四個字節),進棧就是壓棧,所以是往低地址擴展的數據結構。
6、分配方式:
棧:有2種分配方式——靜態分配和動態分配。靜態由編譯器完成,例如局部變量;動態由alloca函數實現,並且編譯器會進行釋放。
堆:都是動態分配的,沒有靜態分配的堆。
7、分配效率不同:
棧:由系統自動分配,速度較快。但程序員是無法控制的。
堆:由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來方便。
四,動態內存分配
1.malloc函數;
函數原型: void * malloc (size_ t size) ;
功能:
1.開辟一塊size大小的連續堆內存。
2.size表示堆 上所開辟內存的大小(字節數)。
3.函數返回值是一個指針,指向剛剛開辟的內存的首地址。
4.如果開辟內存失敗, 返回一個空指針,即返回值為NULL。
5.當內存不再 使用時,應使用free ()函數將內存塊釋放
6.使用時 必須包含頭文件<stdlib.h>或<malloc.h>
2.calloc函數;
函數原型: void * calloc(size_ t n, size t size);
功能:
1.在內存的動態存儲區中分配n個長度為si ze的連續空間,
2.函數返回一個指向分配起始地址的指針;
3.如果分配不成功,返回NULL。
4.當內存不再 使用時,應使用free ()函數將內存塊釋放。
5.使用時 必須包含頭文件<stdlib.h>或<malloc.h>
3.realloc函數;
函數原型:
void * realloc(void * mem_ address, size_ t newsize) ;
功能:
1.為已有內存的變量重新分配新的內存大小(可大、可小) ;
2.先判斷當前的指針是否有足夠的連續空間,如果有,擴大mem_address指向的地址,並且將mem_ address返回;
3.如果空間不夠,先按照newsize指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而后釋放原來mem_address 所指內存區域(注意:原來指針是自動釋放,不需要使用free),同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。
4.如果重新分配成功則返回指向被分配內存的指針;
5.如果分配不成功,返回NULL。
6.當內存不再使用時,應使用free ()函數將內存塊釋放
7.使用時必須包含頭文件<stdlib.h>或<malloc.h>
4.free函數。
函數原型: void free (void *ptr) ; //ptr為要釋放的內存指針。
free():釋放指針變量在堆區上的內存空間,不能釋放棧上的內存空間,free要與malloc(calloc、realloc)成對使用。
注意:
如果malloc(calloc、realloc) 比 free 多, 會造成內存泄漏;
如果malloc(calloc、realloc) 比 free 少,會造成二次刪除, 破壞內存,導致程序崩潰。
參考:https://zhuanlan.zhihu.com/p/55003485
參考:http://c.biancheng.net/cpp/html/2856.html