日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

详解malloc,calloc,realloc原理及其模拟实现

發布時間:2023/11/30 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解malloc,calloc,realloc原理及其模拟实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

malloc原理

malloc它有一個將可用的內存塊連接為一個長長的列表的所謂空閑鏈表。調用malloc函數時,它沿連接表尋找一個大到足以滿足 用戶請求所需要的內存塊。然后,將該內存塊一分為二(一塊的大小與用戶請求的大小相等,另一塊的大小就是剩下的字節)。接下來,將分配給用戶的那塊內存傳 給用戶,并將剩下的那塊(如果有的話)返回到連接表上。調用free函數時,它將用戶釋放的內存塊連接到空閑鏈上。到最后,空閑鏈會被切成很多的小內存片 段,如果這時用戶申請一個大的內存片段,那么空閑鏈上可能沒有可以滿足用戶要求的片段了。于是,malloc函數請求延時,并開始在空閑鏈上翻箱倒柜地檢 查各內存片段,對它們進行整理,將相鄰的小空閑塊合并成較大的內存塊。
查詢鏈表的方法:

break指針

Linux維護一個break指針,這個指針指向堆空間的某個地址。從堆起始地址到break之間的地址空間為映射好的,可以供進程訪問;而從break往上,是未映射的地址空間,如果訪問這段空間則程序會報錯。我們用malloc進行內存分配就是從break往上進行的。

First fit:從頭開始,使用第一個數據區大小大于要求size的塊所謂此次分配的塊。首次適配有更好的運行效率。
Best fit:從頭開始,遍歷所有塊,使用數據區大小大于size且差值最小的塊作為此次分配的塊。最佳適配具有較高的內存使用率。

int brk(void *addr); void *sbrk(intptr_t increment);

brk將break指針直接設置為某個地址;
而sbrk將break從當前位置移動increment所指定的增量,如果將increment設置為0,則可以獲得當前break的地址。

malloc實現:

void* malloc(unsigned size); 在堆內存中分配一塊長度為size字節的連續區域,參數size為需要內存空間的長度。

#include <sys/types.h> #include <unistd.h>typedef struct s_block *t_block;struct s_block {size_t size; // 數據區大小 t_block next; // 指向下個塊的指針 int free; // 是否是空閑塊 int padding; // 填充4字節,保證meta塊長度為8的倍數 char data[1] // 這是一個虛擬字段,表示數據塊的第一個字節,長度不應計入meta };//首次適配 t_block find_block(t_block *last, size_t size) {t_block b = first_block;while(b && !(b->free && b->size >= size)) {*last = b;b = b->next;}return b; }//如果現有block都不能滿足size的要求, //則需要在鏈表最后開辟一個新的block。 //這里關鍵是如何只使用sbrk創建一個struct#define BLOCK_SIZE 24 //由于存在虛擬的data字段,sizeof不能正確計算meta長度,這里手工設置 t_block extend_heap(t_block last, size_t s) {t_block b;b = sbrk(0);if(sbrk(BLOCK_SIZE + s) == (void *)-1)return NULL;b->size = s;b->next = NULL;if(last)last->next = b;b->free = 0;return b; }//First fit有一個比較致命的缺點, //就是可能會讓很小的size占據很大的一塊block, //此時,為了提高payload,應該在剩余數據區足夠大的情況下,將其分裂為一個新的block,void split_block(t_block b, size_t s) {t_block newb;newb = b->data + s;newb->size = b->size - s - BLOCK_SIZE ;newb->next = b->next;newb->free = 1;b->size = s;b->next = newb; }//由于我們希望malloc分配的數據區是按8字節對齊, //所以在size不為8的倍數時,我們需要將size調整為大于size的最小的8的倍數: size_t align8(size_t s) {if(s & 0x7 == 0)return s;return ((s >> 3) + 1) << 3; }void *first_block=NULL;void *malloc(size_t size) {t_block b, last;size_t s;/* 對齊地址 */s = align8(size);if(first_block) {/* 查找合適的block */last = first_block;b = find_block(&last, s);if(b) {<pre name="code" class="cpp"> /* 如果可以,則分裂 */if ((b->size - s) >= ( BLOCK_SIZE + 8))split_block(b, s);b->free = 0;} else {/* 沒有合適的block,開辟一個新的 */b = extend_heap(last, s);if(!b)return NULL;}} else {b = extend_heap(NULL, s);if(!b)return NULL;first_block = b;}return b->data; }

calloc實現:

void* calloc(size_t numElements, size_t sizeOfElement);
與malloc相似,參數sizeOfElement為單位元素長度(例如:sizeof(int)),numElements為元素個數,即在內存中申請numElements * sizeOfElement字節大小的連續內存空間。并且會把內存初始化為0。

calloc(num, size) 基本上等于 void *p = malloc(num * size); memset(p, 0, num * size); 但理論上 calloc 的實現可避免 num * size 溢出,當溢出時返回 NULL 代表失敗,而 malloc(num * size) 可能會分配了一個尺寸溢出后的內存。

由于我們的數據區是按8字節對齊的,所以為了提高效率,我們可以每8字節一組置0,而不是一個一個字節設置。我們可以通過新建一個size_t指針,將內存區域強制看做size_t類型來實現。

void *calloc(size_t number, size_t size) {size_t *news;size_t s8, i;news = malloc(number * size);if(news) {s8 = align8(number * size) >> 3;for(i = 0; i < s8; i++)news[i] = 0;}return news; }

realloc實現:

void* realloc(void* ptr, unsigned newsize);

使用realloc函數為ptr重新分配大小為size的一塊內存空間。下面是這個函數的工作流程:

  • 對ptr進行判斷,如果ptr為NULL,則函數相當于malloc(new_size),試著分配一塊大小為new_size的內存,如果成功將地址返回,否則返回NULL。如果ptr不為NULL,則進入2。
  • 查看ptr是不是在堆中,如果不是的話會拋出realloc invalid pointer異常。如果ptr在堆中,則查看new_size大小,如果new_size大小為0,則相當于free(ptr),將ptr指向的內存空間釋放掉,返回NULL。如果new_size小于原大小,則ptr中的數據可能會丟失,只有new_size大小的數據會保存;如果size等于原大小,等于什么都沒有做;如果size大于原大小,則查看ptr指向的位置還有沒有足夠的連續內存空間,如果有的話,分配更多的空間,返回的地址和ptr相同,如果沒有的話,在更大的空間內查找,如果找到size大小的空間,將舊的內容拷貝到新的內存中,把舊的內存釋放掉,則返回新地址,否則返回NULL。
  • //為了實現realloc,我們首先要實現一個內存復制方法。 //如同calloc一樣,為了效率,我們以8字節為單位進行復制 void copy_block(t_block src, t_block dst) {size_t *sdata, *ddata;size_t i;sdata = src->ptr;ddata = dst->ptr;for(i = 0; (i * 8) < src->size && (i * 8) < dst->size; i++)ddata[i] = sdata[i]; } void *realloc(void *p, size_t size) {size_t s;t_block b, newb;void *newp;if (!p)/* 根據標準庫文檔,當p傳入NULL時,相當于調用malloc */return malloc(size);if(valid_addr(p)){s = align8(size);b = get_block(p);if(b->size >= s){if(b->size - s >= (BLOCK_SIZE + 8))split_block(b,s);} else{/* 看是否可進行合并 */if(b->next && b->next->free&& (b->size + BLOCK_SIZE + b->next->size) >= s){fusion(b);if(b->size - s >= (BLOCK_SIZE + 8))split_block(b, s);}else {/* 新malloc */newp = malloc (s);if (!newp)return NULL;newb = get_block(newp);copy_block(b, new);free(p);return(newp);}}return (p);}return NULL; }

    free實現:

  • 如何驗證所傳入的地址是有效地址,即確實是通過malloc方式分配的數據區首地址
    地址應該在之前malloc所分配的區域內,即在first_block和當前break指針范圍內;
    這個地址確實是之前通過我們自己的malloc分配的。
  • 如何解決碎片問題
  • //首先我們在結構體中增加magic pointer(同時要修改BLOCK_SIZE) typedef struct s_block *t_block;struct s_block {size_t size; // 數據區大小 t_block next; // 指向下個塊的指針 int free; // 是否是空閑塊 int padding; // 填充4字節,保證meta塊長度為8的倍數 char data[1] // 這是一個虛擬字段,表示數據塊的第一個字節,長度不應計入metavoid *ptr; // Magic pointer,指向data }; #define BLOCK_SIZE 24 //我們定義檢查地址合法性的函數: t_block get_block(void *p) {char *tmp;tmp = p;return (p = tmp -= BLOCK_SIZE); }int valid_addr(void *p) {if(first_block) {if(p > first_block && p < sbrk(0)) {return p == (get_block(p))->ptr;}}return 0; }

    將block和相鄰block合并。為了滿足這個實現,需要將s_block改為雙向鏈表。修改后的block結構如下:

    typedef struct s_block *t_block; struct s_block {size_t size; /* 數據區大小 */t_block prev; /* 指向上個塊的指針 */t_block next; /* 指向下個塊的指針 */int free; /* 是否是空閑塊 */int padding; /* 填充4字節,保證meta塊長度為8的倍數 */void *ptr; /* Magic pointer,指向data */char data[1] /* 這是一個虛擬字段,表示數據塊的第一個字節,長度不應計入meta */ }; #define BLOCK_SIZE 28

    合并方法如下:

    t_block fusion(t_block b) {if (b->next && b->next->free) {b->size += BLOCK_SIZE + b->next->size;b->next = b->next->next;if(b->next)b->next->prev = b;}return b; } void free(void *p) {t_block b;if(valid_addr(p)) {b = get_block(p);b->free = 1;if(b->prev && b->prev->free)b = fusion(b->prev);if(b->next)fusion(b);else {if(b->prev)b->prev->prev = NULL;elsefirst_block = NULL;brk(b);}} }

    總結

    以上是生活随笔為你收集整理的详解malloc,calloc,realloc原理及其模拟实现的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。