欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

溫故C語(yǔ)言內(nèi)存管理

 更新時(shí)間:2021年05月06日 14:33:54   作者:超級(jí)大洋蔥806  
這篇文章主要介紹了 C語(yǔ)言內(nèi)存管理的相關(guān)資料,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

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

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

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

  • 內(nèi)存申請(qǐng)未成功,就進(jìn)行使用
  • 內(nèi)存申請(qǐng)成功,但沒有初始化
  • 內(nèi)存初始化成功,但越界訪問
  • 忘記釋放內(nèi)存或者釋放一部分

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

  • 沒有初始化,會(huì)造成內(nèi)存出錯(cuò)
  • 越界訪問內(nèi)存可能導(dǎo)致崩潰
  • 忘記釋放內(nèi)存造成內(nèi)存泄露

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

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

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

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

堆區(qū)(heap)

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

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

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

常量區(qū)

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

代碼段:

代碼段(code segment/text segment)。通常是指用來存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域。代碼區(qū)的指令中包括操作碼和要操作的對(duì)象(或?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語(yǔ)言規(guī)定未顯式初始化的全局變量值默認(rèn)為0)
的全局變量存在BSS段。

3. 棧區(qū)(stack)

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

哪些是分配在棧空間?

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

函數(shù)棧分配:

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

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

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

??臻g的大小:

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

image-20210502151416829

示例代碼:

#include<stdio.h>

struct  A
{};

class  B
{};

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

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

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

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

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

   //測(cè)試棧的大小
   //char   buf[1024 * 1024] = { 'A' };//1M時(shí)崩潰了
   char   buf[1000* 1024] = { 'A' };//棧空間略小于1M

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

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

   return 0;
}

4. 堆區(qū)(heap)

需程序員自己申請(qǐng),并可在運(yùn)行時(shí)指定空間大小,并由程序員手動(dòng)進(jìn)行釋放,容易產(chǎn)生 memory leak。

哪些是分配在堆空間?

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

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

堆空間需要手動(dòng)釋放:

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

堆空間的大小:

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

示例代碼:

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

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

   //手動(dòng)釋放
   free(p);

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

   return 0;
}

棧與堆的區(qū)別:

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

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

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

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

  • 全局變量
  • static靜態(tài)變量

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

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

示例代碼:

#include<stdio.h>

//儲(chǔ)存在全局靜態(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);
}

image-20210502152622950

6. 常量區(qū)

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

示例代碼:

#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);
}

image-20210502152555693

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

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

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

malloc函數(shù):

void *malloc(
  size_t size
);

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

calloc函數(shù):

void *calloc(
  size_t number,
  size_t size
);

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

realloc函數(shù):

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

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

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

  • 如果當(dāng)前連續(xù)內(nèi)存塊足夠 realloc的話,只是將p1所指向的空間擴(kuò)大,并返回p1的指針地址。
  • 如果當(dāng)前連續(xù)內(nèi)存塊不夠長(zhǎng)度,再找一個(gè)足夠長(zhǎng)的地方,分配一塊新的內(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)存空間的值是隨機(jī)的
   int  *p = (int *)malloc(5 *  sizeof(int));
   p[0] = 123;
   for (int i = 0; i < 5; ++i)
   { 
   	printf("%d   ", p[i]); //后面4個(gè)值隨機(jī)
   }
   
   printf("\n------------------------------------------------------------\n " );

   //calloc,參數(shù)兩個(gè), 自動(dòng)將內(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)大一點(diǎn)點(diǎn),兩個(gè)地址相同
   //int  *p3 = (int *)realloc(p, 2 * sizeof(int));//縮小,兩個(gè)地址相同
   int  *p3 = (int *)realloc(p, 100 * sizeof(int));//調(diào)很大,兩個(gè)地址不同 ,釋放原來的內(nèi)存空間
   for (int i = 0; i <2; ++i)
   {
   	printf("%d   ", p3[i]);
   } 

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

   return 0;
}

image-20210502153419886

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

頭文件:

#include <string.h>

strcpy函數(shù)

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

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

注意事項(xiàng):

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ū)域復(fù)制 count個(gè)字節(jié)到dest所指內(nèi)存區(qū)域。

注意事項(xiàng):

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

memmove函數(shù)

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

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

注意事項(xiàng):

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個(gè)字節(jié)的值設(shè)為值c,并返回s。

示例代碼:

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

//模擬memcpy函數(shù)實(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ù)實(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進(jìn)行字符串拷貝  
   //注意:  1. src字符串必須以'\0'結(jié)束,  2. dest內(nèi)存大小必須>=src
   char  a[5];
   //char  b[5] = "ABC";//字符串結(jié)尾會(huì)自動(dòng)的有\(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);//雖然它是正確的,但是不保證,重疊拷貝應(yīng)該避免使用它
   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比較簡(jiǎn)單, 把內(nèi)存區(qū)域初始化化為某個(gè)值
   char a5[6];
   memset(a5, 0, 6);
   for (int i = 0; i < 6; ++i)
   {
   	printf("%d", a5[i]);
   }

   return 0;
}

image-20210502154807752

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

思路:

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

示例代碼:

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

//為了代碼的可讀性,將設(shè)計(jì)為C++中的類,利用struct 代替

//動(dòng)態(tài)數(shù)組
struct  Array
{
   //自動(dòng)構(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ù)組滿了
   	{
   		//需要擴(kuò)大內(nèi)存
   		size += grow; 
   		pHead = (int  *)realloc( pHead,   size * sizeof(int)  );
   		assert(pHead != NULL);
   	}

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

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

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

   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;
}

image-20210502155054643

10. 內(nèi)存越界

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

  • 訪問到野指針指向的區(qū)域,越界訪問
  • 數(shù)組下標(biāo)越界訪問
  • 使用已經(jīng)釋放的內(nèi)存
  • 企圖訪問一段釋放的棧空間
  • 容易忽略 字符串后面的'\0'

注意:

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

示例代碼:

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

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


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

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

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

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

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

   return 0;
}

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

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

  • 丟失了分配的內(nèi)存的首地址,導(dǎo)致無(wú)法釋放
  • 丟失分配的內(nèi)存地址
  • 企圖希望傳入指針變量獲取對(duì)內(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ù)無(wú)任何關(guān)系
{
   p= (char *)malloc(10);
}

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

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

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

   //3.企圖希望傳入指針變量獲取對(duì)內(nèi)存,殊不知是拷貝
   char  *p3 = NULL;
   GetBuf2(p3); //應(yīng)該使用指針的指針,或者引用
   //strcpy(p3, "hello"); //錯(cuò)誤,這里的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只指向最后一次分配的,前面的全部?jī)?nèi)存泄漏

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

   return 0;
}

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

內(nèi)存碎片:

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

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

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

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

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

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

設(shè)計(jì)思路:

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

代碼實(shí)現(xiàn):

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

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

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

   int             nFirst; //當(dāng)前可用空閑單元的序號(hào),從0開始

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

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

   //.....這個(gè)結(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塊的下個(gè)空閑索引是不是第1塊
   		  p += unitSize;
   	 }

   	 *((short *)p) = -1;//最后一塊沒有下一個(gè)空閑空間了,為-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:
   //初始大小 (每一個(gè)MemoryBlock中初始的單元個(gè)數(shù))
   int             nInitCount; 

   //(后面增加的MemoryBlock中單元個(gè)數(shù))   
   int             nGrowSize; 
    
   //分配單元大小,MemoryBlock中每個(gè)單元的大小
   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; 
   	}

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

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

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

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

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

   	MemoryBlock  *  pB = pBlock;
   	while (pB != NULL )
   	{
   		printf("\n首地址:%p   總大?。?d   空閑個(gè)數(shù): %d   下一個(gè)空閑:%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;
}

到此這篇關(guān)于溫故C語(yǔ)言內(nèi)存管理的文章就介紹到這了,更多相關(guān)C語(yǔ)言內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論