malloc calloc realloc free的簡單實現
阿新 • • 發佈:2018-12-30
寫在前面:csdn的部落格排版就是shit,祝早日關門大吉
記憶體分配其實是個必修課,應該清楚地知道一個程式在計算機中的記憶體分佈情況,linux程式在記憶體中的分佈情況是這樣的:
當然啦除了知道諸如“堆從低地址向高地址增長棧從高地址從低地址增長”這種東西之外最好還要知道“什麼是分頁為什麼分頁malloc的內部實現用到了brk()sbrk()MMU是什麼”之類的這種東西。
切入正題,一個簡單的malloc實現如下:
#include <stdio.h>
#include "unistd.h"
#define BLOCK_SIZE 40
typedef struct s_block *t_block;
void *first_block=NULL;
t_block find_block(t_block *last,size_t size);
t_block extend_heap(t_block last, size_t s);
void split_block(t_block b,size_t s);
size_t align8(size_t s);
void *malloc(size_t size);
void *calloc(size_t number, size_t size);
t_block get_block(void *p);
int valid_addr(void *p);
t_block fusion(t_block b);
void free(void *p);
void copy_block(t_block src,t_block dst);
void *realloc(void *p,size_t size);
struct s_block{
size_t size;//資料區大小
t_block prev;//指向上個塊的指標
t_block next;//指向下個塊的指標
int free;//判斷是否是空閒塊
int padding;//填充4位元組,為了迎合結構體對齊,保證meta塊長度為8的倍數
void *ptr;//Magic pointer,指向data
//虛擬欄位這個是真的巧妙!每個塊的s_block後面都是資料區,但是這個data[]不算作s_block的內容
//不計作s_block的長度,所以在訪問s_block的data欄位時實際上訪問的是資料區的第一個位元組
char data[1];
};
int main(void){
return 0;
}
t_block find_block(t_block *last,size_t size){
t_block b=first_block;
while (b && !(b->free && b->size >= size)) {
*last=b;//last用來表示最後一塊可用的記憶體塊(可能是剛剛被釋放過之後的一個塊)
b=b->next;
}
return b;
}
t_block extend_heap(t_block last, size_t s){
t_block b;//強制把新申請出來的記憶體看做是s_block型別的
b = sbrk(0);
if (sbrk(BLOCK_SIZE + s) == (void *) -1) {
return NULL;
}
b->size=s;
b->next=NULL;
b->ptr = &(b->data);//data是一個數組所以其實不用加&
if(last){
last->next=b;
}
b->free=0;
return b;
}
void split_block(t_block b,size_t s){
t_block new;
new=b->data+s;
new->size = b->size - s - BLOCK_SIZE;
new->next = b->next;
new->free=1;
b->size=s;
b->next = new;
}
size_t align8(size_t s){
if (s & 0x7 == 0) {//滿足該條件的s是可以被8整除的
return s;
}
//這樣可以得到一個大於s且能夠被8整除的最小的數
return ((s>>3)+1)<<3;
}
void *malloc(size_t size){
t_block b,last;
size_t s = align8(size);//地址對齊
if (first_block) {
last=first_block;//
b = find_block(&last, s);
if(b){
if ((b->size) >= (BLOCK_SIZE + 8)) {
split_block(b, s);
}
b->free=0;
} else {
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;
}
void *calloc(size_t number, size_t size){
size_t *new;
size_t s8;
new = malloc(number * size);
if (new) {
s8=align8((number*size))>>3;
for (int i = 0; i < s8; i++) {
new[i]=0;
}
}
return new;;
}
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<sbrk(0)&&p>first_block){
return p==(get_block(p))->ptr;//??????
}
}
return 0;
}
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){//傳進來的p實際上是那個block的資料區
t_block b;
if(valid_addr(p)){
b = get_block(p);//b此時指向那塊block的起始地址
b->free=1;//只是標記為空閒,實際上data區還是有資料的
if (b->prev && b->prev->free) {
b=fusion(b->prev);
}
if (b->next) {//檢查當前block是否還有後繼,即檢查是否是最後一個block
fusion(b);//如果有則嘗試合併
}else{//如果沒有後繼則先檢查是否是第一個block
if (b->prev) {//檢查是否有前驅,如果有前驅則表示該block不是第一個
b->prev->next = NULL;
}else{//如果沒有前驅那麼該block既是第一個也是最後一個block
first_block = NULL;//又回到了任何一次malloc之前
}
brk(b);//用brk在heap上進行回退
}
}
}
void copy_block(t_block src,t_block dst){
size_t *sdata, *ddata;
sdata=src->ptr;
ddata=dst->ptr;
for (size_t 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, new;
void *newp;
if(!p){
return malloc(size);
}
if(valid_addr(p)){
s = align8(size);
b = get_block(p);//p是資料區起始,此時b指向那整塊記憶體的起始
if(b->size>=s){//如果這個塊能直接放下原資料大小則對資料什麼也不做
if(b->size-s>=(BLOCK_SIZE+8)){//並順手檢查一下是否可以切割記憶體
split_block(b, s);
}
}else{//如果放不下則檢查一下是否可以合併,允許合併的條件的最後一條是合併之後能放下資料
if (b->next && b->next->free &&
b->next->size + b->size+BLOCK_SIZE >= s) {
fusion(b);
//如果可以合併則在合併完之後檢查是否可以進行分裂
if(b->size-s>=(BLOCK_SIZE+8)){
split_block(b, s);
}
}else{//如果既放不下資料也不符合合併的條件
//那麼就malloc一塊新記憶體
newp = malloc(s);
if(!newp){
return NULL;
}
new = get_block(newp);
copy_block(b, new);
free(p);
return newp;
}
}
return (p);
}
return NULL;
}
這段程式碼不是我的原創,我讀過去之後加了一些註釋方便理解。引用到的一些程式碼在這裡
http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf
只說說閱讀這段程式碼的感受吧。
底層程式碼寫的真的是滴水不漏啊,看的過程中給我一種很嚴謹的感覺。
程式碼中有一些比較取巧的寫法,比如結構體裡的虛擬成員,為了驗證地址是否合法而加的magic pointer,釋放記憶體時不是對記憶體進行覆蓋而是在開頭的指示塊的free中標記為空閒。為了效率在程式碼中多次使用位運算。
總之閱讀這段程式碼收穫還是挺大的。