溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在C語言中實現(xiàn)內(nèi)存管理

發(fā)布時間:2021-05-06 16:25:28 來源:億速云 閱讀:273 作者:Leah 欄目:開發(fā)技術(shù)

這篇文章給大家介紹怎么在C語言中實現(xiàn)內(nèi)存管理,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

C語言是什么

C語言是一門面向過程的、抽象化的通用程序設計語言,廣泛應用于底層開發(fā),使用C語言可以以簡易的方式編譯、處理低級存儲器。

1. 內(nèi)存管理簡介

在計算機系統(tǒng),特別是嵌入式系統(tǒng)中,內(nèi)存資源是非常 有限的。尤其對于移動端開發(fā)者來說,硬件資源的限制使得其在程序設計中首要考慮的問題就是如何 有效地管理內(nèi)存資源。

常見內(nèi)存使用錯誤:

  • 內(nèi)存申請未成功,就進行使用

  • 內(nèi)存申請成功,但沒有初始化

  • 內(nèi)存初始化成功,但越界訪問

  • 忘記釋放內(nèi)存或者釋放一部分

內(nèi)存管理不當?shù)奈:Γ?/strong>

  • 沒有初始化,會造成內(nèi)存出錯

  • 越界訪問內(nèi)存可能導致崩潰

  • 忘記釋放內(nèi)存造成內(nèi)存泄露

C語言的內(nèi)存管理:

C語言為用戶提供了相應內(nèi)存管理的AP接口,如 malloc(),free()new()等函數(shù),需要開發(fā)者手動管理。而java、C#則有自動內(nèi)存回收機制,基本無需再對內(nèi)存進行操作了。

2. 內(nèi)存分類 棧區(qū)(stack)

由系統(tǒng)自動分配

堆區(qū)(heap)

在程序的執(zhí)行過程中才能分配,由程序員決定

全局區(qū)(靜態(tài)區(qū))

靜態(tài)區(qū)存放程序中所有的全局變量和靜態(tài)變量

常量區(qū)

常量字符串就是放在這里的

代碼段:

代碼段(code segment/text segment)。通常是指用來存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域。代碼區(qū)的指令中包括操作碼和要操作的對象(或?qū)ο蟮刂芬茫?。如果是立即?shù)(即具體的數(shù)值,如5)直接包含在代碼中;如果是局部數(shù)據(jù),將在棧區(qū)分配空間,然后引用該數(shù)據(jù)地址。

數(shù)據(jù)段:

數(shù)據(jù)段(data segment)通常是指用來存放程序中已初始化的全局變量的一塊內(nèi)存區(qū)域。數(shù)據(jù)段屬于靜態(tài)內(nèi)存分配。

BSS段:

BSS段(Block Started by Symbol)。指用來存放程序中未初始化的全局變量的一塊內(nèi)存區(qū)域。
BSS段本質(zhì)上也屬于數(shù)據(jù)段,都用來存放C程序中的全局變量。區(qū)別在于.data段中存放初始化為非零的全局變量,而把顯式初始化為0或者并未顯式初始化(C語言規(guī)定未顯式初始化的全局變量值默認為0)
的全局變量存在BSS段。

3. 棧區(qū)(stack)

由編譯器 自動分配釋放,存放函數(shù)的參數(shù)值、局部變量的值等,是一種先進后出的內(nèi)存結(jié)構(gòu)。

哪些是分配在??臻g?

  • 局部變量的值存放在棧上

  • 在函數(shù)體中定義的變量通常是在棧上

函數(shù)棧分配:

在函數(shù)調(diào)用時,第一個進棧的是主函數(shù)中函數(shù)調(diào)用后的下一條指令(函數(shù)調(diào)用語句的下一條可執(zhí)行語句)的地址,然后是函數(shù)的各個參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。

棧內(nèi)存什么時候回收?

棧內(nèi)存的分配和釋放也由編譯器在函數(shù)進入和退出時插入指令自動完成,生命周期和函數(shù)、局部變量一樣。

棧空間的大?。?/strong>

在 Windows下,棧是向低地址擴展的數(shù)據(jù)結(jié)構(gòu),是塊連續(xù)的內(nèi)存的區(qū)域。??臻g一般較小,棧大小與編譯器有關。默認情況下,visual studio 2010的棧大小為1M。但在平時應用程序中,由于函數(shù)會使用棧結(jié)果,所以只能用略小于1M大小的棧如果申請的空間超過棧的剩余空間時,將提示Stack overflow。

怎么在C語言中實現(xiàn)內(nèi)存管理

示例代碼:

#include<stdio.h>

struct  A
{};

class  B
{};

void   fun(int  a  , int  b) //參數(shù)a,b在棧上, 在函數(shù)體結(jié)束的時候,棧內(nèi)存釋放
{
   int   c;//局部變量,在棧上, 在函數(shù)體結(jié)束的時候,棧內(nèi)存釋放
}

int  main()
{
   int  a = 10;//局部變量在棧上,  在main函數(shù)結(jié)束的時候,棧內(nèi)存釋放

   char  b[] = "hello";//數(shù)組變量也在棧上,  在main函數(shù)結(jié)束的時候,棧內(nèi)存釋放

   char  *c = NULL;//在棧上,  在main函數(shù)結(jié)束的時候,棧內(nèi)存釋放

   {
       A   d;//結(jié)構(gòu)體變量,  在棧上 
       B   e;//類對象在棧上
   } //d,e 在離開這個{}時,棧內(nèi)存銷毀釋放

   //測試棧的大小
   //char   buf[1024 * 1024] = { 'A' };//1M時崩潰了
   char   buf[1000* 1024] = { 'A' };//??臻g略小于1M

   //經(jīng)過編譯期設置為5M之后,??臻g變大了
   char   buf[49 * 1024 * 1024 / 10] = { 'A' };//棧空間略小于5M

   printf("%d" , sizeof(buf)  );

   return 0;
}

4. 堆區(qū)(heap)

需程序員自己申請,并可在運行時指定空間大小,并由程序員手動進行釋放,容易產(chǎn)生 memory leak。

哪些是分配在堆空間?

調(diào)用 malloc,realloc,calloc函數(shù)

//分配得來得10*4字節(jié)的區(qū)域在堆區(qū)
p1 = (char*)malloc(10*sizeof(int));

堆空間需要手動釋放:

堆是由 malloc()等函數(shù)分配的內(nèi)存塊,內(nèi)存釋放由程序員調(diào)用free()函數(shù)手動釋放

堆空間的大小:

堆空間一般較大,與64位/32位,編譯器有關,受限于計算機系統(tǒng)中有效的虛擬內(nèi)存;理論上32位系統(tǒng)堆內(nèi)存可以達到4G的空間,實際上2G以內(nèi),64位128G以內(nèi)(虛擬內(nèi)存16TB)

示例代碼:

#include<stdio.h>
#include<stdlib.h>

int  main()
{
   //手動分配、這里就是分配了堆內(nèi)存
   int  *p = (int*)malloc(10 *  sizeof(int ));

   //手動釋放
   free(p);

   int MB = 0;
   while (malloc(1024 * 1024))//每次分配1M
   {
       MB++;
   }
   printf("分配了 %d MB \n", MB); 

   return 0;
}

棧與堆的區(qū)別:


類型分配釋放大小是否連續(xù)申請效率
棧區(qū)由編譯器自動分配釋放較小一塊連續(xù)的內(nèi)存區(qū)域由系統(tǒng)自動分配,速度快
堆區(qū)由程序員分配釋放較大堆是向高地址擴展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域速度慢,容易產(chǎn)生內(nèi)存碎片

5. 全局區(qū)(靜態(tài)區(qū))

全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在塊區(qū)域。

哪些是分配在全局靜態(tài)區(qū)?

  • 全局變量

  • static靜態(tài)變量

全局靜態(tài)區(qū)何時釋放?

全局變量、靜態(tài)變量在整個程序運行的生存期都存在,所以在程序結(jié)束時才釋放

示例代碼:

#include<stdio.h>

//儲存在全局靜態(tài)區(qū)
int   a;  				//全局變量,未初始化
short   b = 10;			//全局變量,已賦值
char  *c = NULL;		//全局變量,已賦值
static  int   f = 200;	//靜態(tài)變量

int  main()
{
   static  int  d = 100;
   static  int  e = 200;

   printf("%p\n", &a);
   printf("%p\n", &b);
   printf("%p\n", &c);
   printf("%p\n", &d);
   printf("%p\n", &e);
   printf("%p\n", &f);
}

怎么在C語言中實現(xiàn)內(nèi)存管理

6. 常量區(qū)

字符串常量是放在常量區(qū),當你初始化賦值的時候,這些常量就先在常量區(qū)開辟一段空間,保存此常量,以后相同的常量就都使用一個地址。

示例代碼:

#include<stdio.h>

//“AAA”是字符串常量,存放在常量區(qū)
char  *p = "AAA";

int  main()
{
   //p1是局部變量,在棧上, “AAA”是字符串常量,存放在常量區(qū)
   char  *p1 = "AAA";

   //p2是局部變量,在棧上,“AAA”不是字符串常量,她只是一種初始化的寫法
   char  p2[]= "AAA";

   //p3是局部變量,在棧上, “AAA”是字符串常量,存放在常量區(qū)
   char  *p3 = "AAA";

   //p4是局部變量,在棧上, “AAB”是字符串常量,存放在常量區(qū)
   char  *p4 = "AAB";

   printf("%p\n", p);
   printf("%p\n", p1);
   printf("%p\n", p2);
   printf("%p\n", p3);
   printf("%p\n", p4);
}

怎么在C語言中實現(xiàn)內(nèi)存管理

7. malloc、calloc、realloc函數(shù)

三個函數(shù)的作用?

它們都能分配堆內(nèi)存、成功返回內(nèi)存的首地址,失敗就返回NULL。

malloc函數(shù):

void *malloc(
  size_t size
);

該函數(shù)將在堆上分配一個 size byte大小的內(nèi)存。不對內(nèi)存進行初始化,所以新內(nèi)存其值將是隨機的。

calloc函數(shù):

void *calloc(
  size_t number,
  size_t size
);

該函數(shù)功能與 malloc相同,它將分配countsize大小的內(nèi)存,自動初始化該內(nèi)存空間為零。

realloc函數(shù):

void *realloc(
  void *memblock,
  size_t size
);

該函數(shù)將ptr內(nèi)存大小增大或減小到newsize

realloc函數(shù)返回的兩種情況:

  • 如果當前連續(xù)內(nèi)存塊足夠 realloc的話,只是將p1所指向的空間擴大,并返回p1的指針地址。

  • 如果當前連續(xù)內(nèi)存塊不夠長度,再找一個足夠長的地方,分配一塊新的內(nèi)存p2,并將p1指向的內(nèi)容Copy到p2,并釋放p1指向的舊內(nèi)存,然后返回p2。

示例代碼:

#include<stdio.h>
#include<stdlib.h>

int  main()
{
   //malloc  ,參數(shù)是字節(jié)數(shù) , 并且這塊內(nèi)存空間的值是隨機的
   int  *p = (int *)malloc(5 *  sizeof(int));
   p[0] = 123;
   for (int i = 0; i < 5; ++i)
   { 
   	printf("%d   ", p[i]); //后面4個值隨機
   }
   
   printf("\n------------------------------------------------------------\n " );

   //calloc,參數(shù)兩個, 自動將內(nèi)存空間初始化為0
   int   *p2 = (int *)calloc(5, sizeof(int));
   p2[4] = 123;
   for (int i = 0; i < 5; ++i)
   {
   	printf("%d   ", p2[i]); 
   }

   printf("\n------------------------------------------------------------\n ");
    
   //realloc ,可以調(diào)整內(nèi)存空間的大小 ,并且拷貝原來的內(nèi)容(調(diào)大,或者  縮小)
   //int  *p3 =(int *) realloc(p, 6* sizeof(int));//調(diào)大一點點,兩個地址相同
   //int  *p3 = (int *)realloc(p, 2 * sizeof(int));//縮小,兩個地址相同
   int  *p3 = (int *)realloc(p, 100 * sizeof(int));//調(diào)很大,兩個地址不同 ,釋放原來的內(nèi)存空間
   for (int i = 0; i <2; ++i)
   {
   	printf("%d   ", p3[i]);
   } 

   printf("\np地址:  %p   ,  p3的地址:  %p   ", p,  p3);

   return 0;
}

怎么在C語言中實現(xiàn)內(nèi)存管理

8. strcpy、memcpy、memmove函數(shù)

頭文件:

#include <string.h>

strcpy函數(shù)

char *strcpy(
  char *strDestination,
  const char *strSource
);

src所指由\0結(jié)束的字符串復制到dest所指的數(shù)組中。

注意事項:

srcdest所指內(nèi)存區(qū)域不能重疊,且dest必須有足夠的空間來容納src的字符串,src的結(jié)尾必須是'\0',返回指向dest的指針。

memcpy函數(shù)

void *memcpy(
  void *dest,
  const void *src,
  size_t count
);

src所指內(nèi)存區(qū)域復制 count個字節(jié)到dest所指內(nèi)存區(qū)域。

注意事項:

函數(shù)返回指向dest的指針和 strcpy相比,memcpy不是遇到\0就結(jié)束,而一定會拷貝n個字節(jié)注意srcdest所指內(nèi)存區(qū)域不能重疊,否則不能保證正確。

memmove函數(shù)

void *memmove(
  void *dest,
  const void *src,
  size_t count
);

函數(shù)功能:與 memcpy相同。

注意事項:

srcdest所指內(nèi)存區(qū)域可以重疊,memmove可保證拷貝結(jié)果正確,而memcpy不能保證。函數(shù)返回指向dest的指針。

memset函數(shù)

void *memset(
  void *dest,
  int c,
  size_t count
);

常用于內(nèi)存空間的初始化。將已開辟內(nèi)存空間s的首n個字節(jié)的值設為值c,并返回s。

示例代碼:

#include<stdio.h> 
#include<string.h>
#include<assert.h> 

//模擬memcpy函數(shù)實現(xiàn)
void  *  MyMemcpy(void *dest, const void *source, size_t count)
{
   assert((NULL != dest) && (NULL != source));
   char *tmp_dest = (char *)dest;
   char *tmp_source = (char *)source;
   while (count--)//不判斷是否重疊區(qū)域拷貝
   	*tmp_dest++ = *tmp_source++;
   return dest;
}

//模擬memmove函數(shù)實現(xiàn)
void * MyMemmove(void *dest, const void *src, size_t n)
{
   char temp[256];
   int i;
   char *d =(char*) dest;
   const char *s =(char *) src;
   for (i = 0; i < n; i++)
   	temp[i] = s[i];
   for (i = 0; i < n; i++)
   	d[i] = temp[i];
   return dest;
}

int  main()
{
    //strcpy進行字符串拷貝  
   //注意:  1. src字符串必須以'\0'結(jié)束,  2. dest內(nèi)存大小必須>=src
   char  a[5];
   //char  b[5] = "ABC";//字符串結(jié)尾會自動的有\(zhòng)0 , 此處 b[4]就是'\0' 
   char  b[5];
   b[0] = 'A';
   b[1] = 'B';
   b[2] = 'C';
   b[3] = '\0';//必須加\0,否則strcpy一直向后尋找\0
   strcpy(a, b);
   printf("%s\n", a);

   //memcpy函數(shù), 直接拷貝內(nèi)存空間,指定拷貝的大小
   int   a2[5];
   int   b2[5] = { 1,2,3,4,5 };//不需要'\0'結(jié)束
   memcpy(a2, b2,   3 *sizeof(int)   );//指定拷貝的大小, 單位  字節(jié)數(shù)
   printf("%d , %d  ,%d\n" , a2[0] ,  a2[1],  a2[2]);

   MyMemcpy(a2 + 3, b2 + 3,   2 * sizeof(int));
   printf("%d , %d \n", a2[3], a2[4]);

   //演示內(nèi)存重疊的情況
   char  a3[6] = "123";
   //MyMemcpy(a3 + 1, a3, 4); //得到11111
   memcpy(a3 + 1, a3, 4);//雖然它是正確的,但是不保證,重疊拷貝應該避免使用它
   printf("%s\n", a3);

   //memmove功能與memcpy一樣,但是了考慮了重疊拷貝的問題,可以保證正確
   char  a4[6] = "123";
   //MyMemmove(a4 + 1, a4, 4);//可以保證正確
   memmove(a4 + 1, a4, 4);//可以保證正確
   printf("%s\n", a4);


   //memset比較簡單, 把內(nèi)存區(qū)域初始化化為某個值
   char a5[6];
   memset(a5, 0, 6);
   for (int i = 0; i < 6; ++i)
   {
   	printf("%d", a5[i]);
   }

   return 0;
}

怎么在C語言中實現(xiàn)內(nèi)存管理

9. 實現(xiàn)動態(tài)數(shù)組

思路:

利用 realloc函數(shù),當數(shù)組元素滿的時候,擴充內(nèi)存區(qū)域,然后加入元素!

示例代碼:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//為了代碼的可讀性,將設計為C++中的類,利用struct 代替

//動態(tài)數(shù)組
struct  Array
{
   //自動構(gòu)造函數(shù),它初始化
   Array()
   {
   	grow = 3;
   	size = 3;
   	n = 0;
   	//分配并初始化內(nèi)存
   	pHead = (int  *)calloc(size  , sizeof(int));
   	assert(pHead != NULL); 
   }

   void   AddElem(int  e)
   {
   	if (n >= size)//說明數(shù)組滿了
   	{
   		//需要擴大內(nèi)存
   		size += grow; 
   		pHead = (int  *)realloc( pHead,   size * sizeof(int)  );
   		assert(pHead != NULL);
   	}

   	pHead[n++] = e; //添加元素
   }

   void  Print()
   {
   	printf("\n\n數(shù)組總空間:%d   ,   元素個數(shù): %d  \n",  size,  n);
   	for (int i = 0; i < n; ++i)
   	{
   		printf("%d  " ,  pHead[i]);
   	} 
   }

   int   size;//總空間, 不是固定的,可以增大的
   int   n;//當前數(shù)組的元素個數(shù)
   int   grow;//每次數(shù)組內(nèi)存滿了的時候,增長量

   int   *pHead;//數(shù)組的起始地址
};

int  main()
{
   Array  arr; 

   arr.AddElem(1);
   arr.AddElem(2);
   arr.AddElem(3);
   arr.AddElem(4);
   arr.AddElem(5);
   arr.AddElem(6);
   arr.AddElem(7);
   arr.AddElem(8);  
   arr.Print(); 

   arr.AddElem(11);
   arr.AddElem(22);
   arr.AddElem(33);
   arr.AddElem(44);
   arr.AddElem(55);  
   arr.Print();

   return  0;
}

怎么在C語言中實現(xiàn)內(nèi)存管理

10. 內(nèi)存越界

何謂內(nèi)存訪問越界,簡單的說,你向系統(tǒng)申請了一塊內(nèi)存,在使用這塊內(nèi)存的時候,超出了你申請的范圍。

  • 訪問到野指針指向的區(qū)域,越界訪問

  • 數(shù)組下標越界訪問

  • 使用已經(jīng)釋放的內(nèi)存

  • 企圖訪問一段釋放的??臻g

  • 容易忽略 字符串后面的'\0'

注意:

strlen所作的是一個計數(shù)器的工作,它從內(nèi)存的某個位置(可以是字符串開頭,中間某個位置,甚至是某個不確定的內(nèi)存區(qū)域)開始掃描,直到碰到第一個字符串結(jié)束符'\0'為止,然后返回計數(shù)器值( 長度不包含'\0')。

示例代碼:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char  * fun()
{
   char arr[10];
    return  arr;
}//arr是棧內(nèi)存,離開此花括號,棧被釋放回收


int main()
{
   //1.訪問到野指針指向的區(qū)域,越界訪問
   char  *p;//沒有初始化,野指針,亂指一氣
   //strcpy(p, "hello");//非法越界訪問

   //2.數(shù)組下標越界訪問
   int   * p2 = (int *)calloc(10, sizeof(int));
   for (size_t i = 0; i <= 10; i++)
   {
   	p2[i] = i;//很難察覺的越界訪問, 下標越界
   }

   //3.使用已經(jīng)釋放的內(nèi)存
   char *p3 = (char *)malloc(10);
   free(p3);
   if (p3 != NULL)//這里if不起作用
   {
   	strcpy(p3, "hello");//錯誤,p3已經(jīng)被釋放
   }

   //4.企圖訪問一段釋放的??臻g
   char *p4 = fun();  //p4指向的棧空間已經(jīng)被釋放
   strcpy(p4, "hello");
   printf("%s\n",p4);

   //5.容易忽略 字符串后面的'\0'
   char  *p5 = (char *)malloc(strlen("hello"));//忘記加1
   strcpy(p5, "hello");//導致p5的長度不夠,越界

   return 0;
}

11. 內(nèi)存泄露(Memory Leak)

是指程序中己動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內(nèi)存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果。

  • 丟失了分配的內(nèi)存的首地址,導致無法釋放

  • 丟失分配的內(nèi)存地址

  • 企圖希望傳入指針變量獲取對內(nèi)存,殊不知是拷貝

  • 每循環(huán)一次,泄露一次內(nèi)存

  • 非法訪問常量區(qū)

示例代碼:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>


char  * GetBuf()
{
   return  (char *)malloc(10);
}

void  GetBuf2(char *p)//p已經(jīng)是一份拷貝,和原參數(shù)無任何關系
{
   p= (char *)malloc(10);
}

char  * GetBuf3()
{
   char  *p = "hello";//常量內(nèi)存區(qū),不可更改
   return p;
}

int main()
{
   //1.丟失了分配的內(nèi)存的首地址,導致無法釋放
   GetBuf();//忘記接收返回值了

   //2.丟失分配的內(nèi)存地址
   char  *p1= (char *)malloc(10);
   char  *p2 = (char *)malloc(10);
   p1 = p2;//這一步,導致第一次分配的堆內(nèi)存丟失,無法釋放

   //3.企圖希望傳入指針變量獲取對內(nèi)存,殊不知是拷貝
   char  *p3 = NULL;
   GetBuf2(p3); //應該使用指針的指針,或者引用
   //strcpy(p3, "hello"); //錯誤,這里的p3仍然為NULL

   //4.每循環(huán)一次,泄露一次內(nèi)存
   char  * p4 = NULL;
   for (int i = 0; i < 10; ++i)
   {
   	p4= (char *)malloc(10);
   }
   strcpy(p4, "hello"); // 這里的p4只指向最后一次分配的,前面的全部內(nèi)存泄漏

   //5.非法訪問常量區(qū)
   char *p5 = GetBuf3();
   strcpy(p5, "hello");  

   return 0;
}

12. 內(nèi)存池技術(shù)簡介

內(nèi)存碎片:

內(nèi)存碎片一般是由于空閑的內(nèi)存空間比要連續(xù)申請的空間小,導致這些小內(nèi)存塊不能被充分的利用,當你需要分配大的連續(xù)內(nèi)存時,盡管剩余內(nèi)存的總和足夠,但系統(tǒng)找不到連續(xù)的內(nèi)存,所以導致分配失敗malloc/free大量使用會造成內(nèi)存碎片

為什么會產(chǎn)生內(nèi)存碎片?

如果有100個單位的連續(xù)空閑內(nèi)存,那么先申請5單元的連續(xù)內(nèi)存,再申請50單元的內(nèi)存這時釋放一開始的5單元的內(nèi)存。這時,如果你一直申請比5單元大的內(nèi)存單元,那么開始的那連續(xù)的5單元就一直不能被使用。

內(nèi)存池技術(shù):

內(nèi)存的申請、釋放是低效的,我們只在開始申請一塊大內(nèi)存(不夠繼續(xù)申請),然后每次需要時都從這塊內(nèi)存取出,并標記這塊內(nèi)存是否被使用。釋放時僅僅標記而不真的free,只有內(nèi)存都空閑的時候,才釋放給操作系統(tǒng)。這樣減少了 mallocfree次數(shù),從而提高效率。

13. C語言實現(xiàn)內(nèi)存池

設計思路:

先分配幾個大的連續(xù)內(nèi)存塊(MemoryBlock),每個內(nèi)存塊用鏈表鏈接起來,然后通過一個內(nèi)存池結(jié)構(gòu)(MemoryPool)管理!

代碼實現(xiàn):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

class  MemoryBlock
{
public:
   int             nSize; //該內(nèi)存塊的總大小 (單元個數(shù)X每個單元大小),以字節(jié)為單位

   int             nFree;  //該內(nèi)存塊還有多少個空閑的單元

   int             nFirst; //當前可用空閑單元的序號,從0開始

   MemoryBlock*    pNext;  //指向下一個MemoryBlock內(nèi)存塊

   char            aData[1]; //用于標記分配內(nèi)存開始的位置  

   //.....這個結(jié)構(gòu)下面全是內(nèi)存

public:
   MemoryBlock(int  unitCount, int  unitSize)
   {
   	 nSize = unitCount* unitSize;
   	 nFree = unitCount;
   	 nFirst = 0;
   	 pNext = NULL;

   	 char  *p = aData;//獲取內(nèi)存單元的首地址

   	 for (int i = 0; i < unitCount -1; ++i)
   	 {
   		 *((short *)p) = i + 1; //第0塊的下個空閑索引是不是第1塊
   		  p += unitSize;
   	 }

   	 *((short *)p) = -1;//最后一塊沒有下一個空閑空間了,為-1
   }

   void  *  operator new (size_t  t,  int   size)
   {
   	 int  headSize = sizeof(MemoryBlock);
         return   ::operator  new(headSize + size);
   }
};

//分配固定內(nèi)存的內(nèi)存池
class  MemoryPool
{

public:
   //初始大小 (每一個MemoryBlock中初始的單元個數(shù))
   int             nInitCount; 

   //(后面增加的MemoryBlock中單元個數(shù))   
   int             nGrowSize; 
    
   //分配單元大小,MemoryBlock中每個單元的大小
   int             nUnitSize; 

   //內(nèi)存塊鏈表
   MemoryBlock*    pBlock;

public:
   MemoryPool( int  _nInitCount, int  _nGrowSize, int _nUnitSize)
   {
   	nInitCount = _nInitCount;
   	nGrowSize = _nGrowSize;
   	nUnitSize = _nUnitSize;
   	pBlock = NULL;
   }

   char  *  Alloc() //每次只返回 nUnitSize 大小的內(nèi)存
   {
   	if (pBlock == NULL)
   	{
   		 MemoryBlock  *   p =(MemoryBlock  *) new (nInitCount * nUnitSize) MemoryBlock(nInitCount, nUnitSize);
   		 assert(p != NULL);

   		 pBlock = p;
   	}

   	MemoryBlock  *  pB = pBlock;
   	while (pB !=NULL  &&   pB->nFree==0)
   	{
   		pB = pB->pNext;
   	}

   	if (pB == NULL)//一直沒找到了可以分配的MemoryBlock,說明內(nèi)存池已滿
   	{
   		pB = (MemoryBlock  *) new (nGrowSize  * nUnitSize) MemoryBlock(nGrowSize, nUnitSize);
   		assert(pB != NULL);

   		pB->pNext = pBlock;
   		pBlock = pB; 
   	}

   	//得到第一個可用的空閑內(nèi)存地址
   	char *pFree = pB->aData + pB->nFirst * nUnitSize;
   	//把nFirst值改為下一個空閑的索引 (存儲在當前內(nèi)存的前兩個字節(jié))
   	pB->nFirst = *((short*)pFree);
   	pB->nFree--;
   	return  pFree;  
   }

   void   Free(void  *p)
   {
     //考慮這個地址落在哪個 MemoryBlock 上
   	MemoryBlock  *  pB = pBlock;
   	while (pB  != NULL  &&   p <  pB->aData   ||  p > pB->aData+  pB->nSize   )
   	{
   		pB = pB->pNext;
   	}
   	 
   	if (pB!= NULL)//找到了p所在的MemoryBlock
   	{
   		 //銷毀之前先讓它的前兩個字節(jié)指向nFirst (當前空閑的索引)
   		*((short*)p) = pB->nFirst;

   		 //nFirst的值指向當前釋放的
   		 pB->nFirst = ((char *)p - pB->aData) / nUnitSize;

   		 pB->nFree++;
   	}
   	else
   	{
   		printf("錯誤,此內(nèi)存并非內(nèi)存池分配的!\n");
   	}
   }

   void  Print()
   {
   	printf("\n\n\n");

   	MemoryBlock  *  pB = pBlock;
   	while (pB != NULL )
   	{
   		printf("\n首地址:%p   總大?。?d   空閑個數(shù): %d   下一個空閑:%d  \n",
   			pB->aData ,  pB->nSize, pB->nFree ,pB->nFirst);

   		for (int i = 0; i < pB->nSize / nUnitSize; ++i)
   		{
   			printf("\t %d" ,  *  ((int *) ( pB->aData + i * nUnitSize )));
   		}
   		 
   		pB = pB->pNext;

   		printf("\n---------------------------------------------------------\n");
   	}
   }
};

int main()
{
   MemoryPool   pool(3, 3, 4);

   int  *p1 = (int *)pool.Alloc();
   *p1 = 111;

   int  *p2 = (int *)pool.Alloc();
   *p2 = 222;
    
   int  *p3 = (int *)pool.Alloc();
   *p3 = 333;

   pool.Print();

   int  *p4 = (int *)pool.Alloc();
   *p4 = 444;

   pool.Print();


   int  *p5 = (int *)pool.Alloc();
   *p5 = 555;

   pool.Print();

   pool.Free( p1);
   pool.Free(p2);
   pool.Free(p3);
   pool.Print();

    p1 = (int *)pool.Alloc();
   *p1 = 111;

    p2 = (int *)pool.Alloc();
   *p2 = 222;

    p3 = (int *)pool.Alloc();
   *p3 = 333;
   pool.Print();

   return 0;
}

關于怎么在C語言中實現(xiàn)內(nèi)存管理就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI