Windows下的 malloc 原理就是調(diào)用 windows API 的各種內(nèi)存管理函數(shù)申請(qǐng)內(nèi)存并記錄內(nèi)存狀態(tài)以便將來(lái)釋放。
創(chuàng)新互聯(lián)建站客戶idc服務(wù)中心,提供服務(wù)器托管、成都服務(wù)器、成都主機(jī)托管、成都雙線服務(wù)器等業(yè)務(wù)的一站式服務(wù)。通過(guò)各地的服務(wù)中心,我們向成都用戶提供優(yōu)質(zhì)廉價(jià)的產(chǎn)品以及開(kāi)放、透明、穩(wěn)定、高性價(jià)比的服務(wù),資深網(wǎng)絡(luò)工程師在機(jī)房提供7*24小時(shí)標(biāo)準(zhǔn)級(jí)技術(shù)保障。
DOS下的 malloc 原理就是調(diào)用申請(qǐng)內(nèi)存的中斷申請(qǐng)內(nèi)存并記錄內(nèi)存狀態(tài)以便將來(lái)釋放。
UNIX 和 Linux 都有內(nèi)存管理的系統(tǒng)調(diào)用,malloc 相當(dāng)于給這些系統(tǒng)調(diào)用穿了一件
malloc()工作機(jī)制
malloc函數(shù)的實(shí)質(zhì)體現(xiàn)在,它有一個(gè)將可用的內(nèi)存塊連接為一個(gè)長(zhǎng)長(zhǎng)的列表的所謂空閑鏈表。調(diào)用malloc函數(shù)時(shí),它沿連接表尋找一個(gè)大到足以滿足用戶請(qǐng)求所需要的內(nèi)存塊。然后,將該內(nèi)存塊一分為二(一塊的大小與用戶請(qǐng)求的大小相等,另一塊的大小就是剩下的字節(jié))。接下來(lái),將分配給用戶的那塊內(nèi)存?zhèn)鹘o用戶,并將剩下的那塊(如果有的話)返回到連接表上。調(diào)用free函數(shù)時(shí),它將用戶釋放的內(nèi)存塊連接到空閑鏈上。到最后,空閑鏈會(huì)被切成很多的小內(nèi)存片段,如果這時(shí)用戶申請(qǐng)一個(gè)大的內(nèi)存片段,那么空閑鏈上可能沒(méi)有可以滿足用戶要求的片段了。于是,malloc函數(shù)請(qǐng)求延時(shí),并開(kāi)始在空閑鏈上翻箱倒柜地檢查各內(nèi)存片段,對(duì)它們進(jìn)行整理,將相鄰的小空閑塊合并成較大的內(nèi)存塊。
malloc()在操作系統(tǒng)中的實(shí)現(xiàn)
在 C 程序中,多次使用malloc () 和 free()。不過(guò),您可能沒(méi)有用一些時(shí)間去思考它們?cè)谀牟僮飨到y(tǒng)中是如何實(shí)現(xiàn)的。本節(jié)將向您展示 malloc 和 free 的一個(gè)最簡(jiǎn)化實(shí)現(xiàn)的代碼,來(lái)幫助說(shuō)明管理內(nèi)存時(shí)都涉及到了哪些事情。
在大部分操作系統(tǒng)中,內(nèi)存分配由以下兩個(gè)簡(jiǎn)單的函數(shù)來(lái)處理:
void *malloc (long numbytes):該函數(shù)負(fù)責(zé)分配 numbytes 大小的內(nèi)存,并返回指向第一個(gè)字節(jié)的指針。
void free(void *firstbyte):如果給定一個(gè)由先前的 malloc 返回的指針,那么該函數(shù)會(huì)將分配的空間歸還給進(jìn)程的“空閑空間”。
malloc_init 將是初始化內(nèi)存分配程序的函數(shù)。它要完成以下三件事:將分配程序標(biāo)識(shí)為已經(jīng)初始化,找到系統(tǒng)中最后一個(gè)有效內(nèi)存地址,然后建立起指向我們管理的內(nèi)存的指針。這三個(gè)變量都是全局變量:
//清單 1. 我們的簡(jiǎn)單分配程序的全局變量
int has_initialized = 0;
void *managed_memory_start;
void *last_valid_address;
如前所述,被映射的內(nèi)存的邊界(最后一個(gè)有效地址)常被稱為系統(tǒng)中斷點(diǎn)或者 當(dāng)前中斷點(diǎn)。在很多 UNIX? 系統(tǒng)中,為了指出當(dāng)前系統(tǒng)中斷點(diǎn),必須使用 sbrk(0) 函數(shù)。 sbrk 根據(jù)參數(shù)中給出的字節(jié)數(shù)移動(dòng)當(dāng)前系統(tǒng)中斷點(diǎn),然后返回新的系統(tǒng)中斷點(diǎn)。使用參數(shù) 0 只是返回當(dāng)前中斷點(diǎn)。這里是我們的 malloc 初始化代碼,它將找到當(dāng)前中斷點(diǎn)并初始化我們的變量:
清單 2. 分配程序初始化函數(shù)
#include
void malloc_init()
{
last_valid_address = sbrk(0);
managed_memory_start = last_valid_address;
has_initialized = 1;
}
現(xiàn)在,為了完全地管理內(nèi)存,我們需要能夠追蹤要分配和回收哪些內(nèi)存。在對(duì)內(nèi)存塊進(jìn)行了 free 調(diào)用之后,我們需要做的是諸如將它們標(biāo)記為未被使用的等事情,并且,在調(diào)用 malloc 時(shí),我們要能夠定位未被使用的內(nèi)存塊。因此, malloc 返回的每塊內(nèi)存的起始處首先要有這個(gè)結(jié)構(gòu):
//清單 3. 內(nèi)存控制塊結(jié)構(gòu)定義
struct mem_control_block {
int is_available;
int size;
};
現(xiàn)在,您可能會(huì)認(rèn)為當(dāng)程序調(diào)用 malloc 時(shí)這會(huì)引發(fā)問(wèn)題 —— 它們?nèi)绾沃肋@個(gè)結(jié)構(gòu)?答案是它們不必知道;在返回指針之前,我們會(huì)將其移動(dòng)到這個(gè)結(jié)構(gòu)之后,把它隱藏起來(lái)。這使得返回的指針指向沒(méi)有用于任何其他用途的內(nèi)存。那樣,從調(diào)用程序的角度來(lái)看,它們所得到的全部是空閑的、開(kāi)放的內(nèi)存。然后,當(dāng)通過(guò) free() 將該指針傳遞回來(lái)時(shí),我們只需要倒退幾個(gè)內(nèi)存字節(jié)就可以再次找到這個(gè)結(jié)構(gòu)。
在討論分配內(nèi)存之前,我們將先討論釋放,因?yàn)樗?jiǎn)單。為了釋放內(nèi)存,我們必須要做的惟一一件事情就是,獲得我們給出的指針,回退 sizeof(struct mem_control_block) 個(gè)字節(jié),并將其標(biāo)記為可用的。這里是對(duì)應(yīng)的代碼:
清單 4. 解除分配函數(shù)
void free(void *firstbyte) {
struct mem_control_block *mcb;
mcb = firstbyte - sizeof(struct mem_control_block);
mcb-is_available = 1;
return;
}
如您所見(jiàn),在這個(gè)分配程序中,內(nèi)存的釋放使用了一個(gè)非常簡(jiǎn)單的機(jī)制,在固定時(shí)間內(nèi)完成內(nèi)存釋放。分配內(nèi)存稍微困難一些。我們主要使用連接的指針遍歷內(nèi)存來(lái)尋找開(kāi)放的內(nèi)存塊。這里是代碼:
//清單 6. 主分配程序
void *malloc(long numbytes) {
void *current_location;
struct mem_control_block *current_location_mcb;
void *memory_location;
if(! has_initialized) {
malloc_init();
}
numbytes = numbytes + sizeof(struct mem_control_block);
memory_location = 0;
current_location = managed_memory_start;
while(current_location != last_valid_address)
{
current_location_mcb =
(struct mem_control_block *)current_location;
if(current_location_mcb-is_available)
{
if(current_location_mcb-size = numbytes)
{
current_location_mcb-is_available = 0;
memory_location = current_location;
break;
}
}
current_location = current_location +
current_location_mcb-size;
}
if(! memory_location)
{
sbrk(numbytes);
memory_location = last_valid_address;
last_valid_address = last_valid_address + numbytes;
current_location_mcb = memory_location;
current_location_mcb-is_available = 0;
current_location_mcb-size = numbytes;
}
memory_location = memory_location + sizeof(struct mem_control_block);
return memory_location;
}
這就是我們的內(nèi)存管理器。現(xiàn)在,我們只需要構(gòu)建它,并在程序中使用它即可.多次調(diào)用malloc()后空閑內(nèi)存被切成很多的小內(nèi)存片段,這就使得用戶在申請(qǐng)內(nèi)存使用時(shí),由于找不到足夠大的內(nèi)存空間,malloc()需要進(jìn)行內(nèi)存整理,使得函數(shù)的性能越來(lái)越低。聰明的程序員通過(guò)總是分配大小為2的冪的內(nèi)存塊,而最大限度地降低潛在的malloc性能喪失。也就是說(shuō),所分配的內(nèi)存塊大小為4字節(jié)、8字節(jié)、16字節(jié)、 18446744073709551616字節(jié),等等。這樣做最大限度地減少了進(jìn)入空閑鏈的怪異片段(各種尺寸的小片段都有)的數(shù)量。盡管看起來(lái)這好像浪費(fèi)了空間,但也容易看出浪費(fèi)的空間永遠(yuǎn)不會(huì)超過(guò)50%。
這與C語(yǔ)言自身無(wú)關(guān),屬于運(yùn)行時(shí)操作系統(tǒng)的問(wèn)題,應(yīng)該借助于操作系統(tǒng)的進(jìn)程內(nèi)存分配去向操作系統(tǒng)查詢。如果用C語(yǔ)言的內(nèi)存管理函數(shù)去計(jì)算,會(huì)相當(dāng)麻煩,而且計(jì)算方法隨著操作系統(tǒng)平臺(tái)的不同而變化。
如果是在Windows環(huán)境運(yùn)行,可以使用Windows的API函數(shù)GlobalMemoryStatusEx,返回的結(jié)構(gòu)中dwTotalVirtual的值與最大值之間的差,就是當(dāng)前進(jìn)程所占用的內(nèi)存空間大小。
動(dòng)態(tài)內(nèi)存分配即分配內(nèi)存大小在運(yùn)行時(shí)才確定,一般在堆中分配。C語(yǔ)言動(dòng)態(tài)內(nèi)存分配相關(guān)的函數(shù)。
malloc
#include stdlib.h
void * malloc (size_t size);
malloc的使用比較直接,一個(gè)成功的malloc調(diào)用返回分配的size大小的內(nèi)存的指針。失敗時(shí)返回NULL并將錯(cuò)誤代碼置為ENOMEM。
教材中經(jīng)常出現(xiàn)的用法是將malloc返回的void指針進(jìn)行強(qiáng)制內(nèi)存轉(zhuǎn)換然后賦給內(nèi)存指針,其實(shí)是不必要的,在賦值時(shí)C語(yǔ)言是可以將void類型指針自動(dòng)轉(zhuǎn)換成對(duì)應(yīng)的指針的。
calloc
#include stdlib.h
void * calloc (size_t nr, size_t size);
calloc可以分配nr個(gè)size大小的內(nèi)存空間,一般用于一組struct結(jié)構(gòu)體的分配。
那么calloc和malloc有什么區(qū)別呢?拋開(kāi)nr參數(shù)不談(malloc也可以將參數(shù)設(shè)置為nr*size達(dá)到一樣的效果),最關(guān)鍵的區(qū)別是malloc分配的內(nèi)存是不保證初始化的,而calloc會(huì)將分配的內(nèi)存都初始化為0.
realloc
#include stdlib.h
void * realloc (void *ptr, size_t size);
realloc函數(shù)將ptr指向的內(nèi)存空間重新分配大小為size并返回新的內(nèi)存首地址。具體的實(shí)現(xiàn),函數(shù)首先會(huì)嘗試直接在已經(jīng)分配的內(nèi)存后進(jìn)行padding,如果空間足夠那么還是返回原來(lái)的地址,如果不夠,則會(huì)尋找新的空間并malloc size個(gè)字節(jié),之后再將原先的內(nèi)容“搬家”到新的內(nèi)存地址,所以函數(shù)的返回值可能和原指針相同,也可能不同。
另外,size參數(shù)如果是0,則該函數(shù)和free效果相同。如果ptr是NULL,函數(shù)的效果和malloc相同~
free
#include stdlib.h
void free (void *ptr);
釋放前三個(gè)函數(shù)申請(qǐng)的內(nèi)存空間。關(guān)于free最經(jīng)典的問(wèn)題就是內(nèi)存泄露(memory leak)。所以,使用前三個(gè)分配函數(shù)分配的內(nèi)存一定要記得free掉。
1、獲得函數(shù)的地址
函數(shù)名實(shí)際上就是指針,所以可以通過(guò)強(qiáng)制類型轉(zhuǎn)換來(lái)獲取函數(shù)的地址。
int i = 0;
i = (int)main;
printf(“%d\n”,i);
這樣我們就獲取了main函數(shù)的地址了,其他的自定義的函數(shù)也是同樣的道理啊。
2、獲取變量地址
int variab = 0;
int addrvar = 0;
addrvar = (int)variab;
在變量addrvar中保存了變量variab的地址。
3、獲得數(shù)組的地址
數(shù)組本身就是指針,它本身就是內(nèi)存地址,和獲得函數(shù)地址的方法一樣啊。比如
int array[10];
int addrarr;
addrarr = (int)array;