溫故C語言內(nèi)存管理
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?
- 局部變量的值存放在棧上
- 在函數(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é)構,是塊連續(xù)的內(nèi)存的區(qū)域。棧空間一般較小,棧大小與編譯器有關。默認情況下,visual studio 2010的棧大小為1M。但在平時應用程序中,由于函數(shù)會使用棧結(jié)果,所以只能用略小于1M大小的棧如果申請的空間超過棧的剩余空間時,將提示Stack overflow。

示例代碼:
#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é)構體變量, 在棧上
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é)構,是不連續(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);
}

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

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相同,它將分配count個size大小的內(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;
}

8. strcpy、memcpy、memmove函數(shù)
頭文件:
#include <string.h>
strcpy函數(shù)
char *strcpy( char *strDestination, const char *strSource );
把src所指由\0結(jié)束的字符串復制到dest所指的數(shù)組中。
注意事項:
src和dest所指內(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é)注意src和dest所指內(nèi)存區(qū)域不能重疊,否則不能保證正確。
memmove函數(shù)
void *memmove( void *dest, const void *src, size_t count );
函數(shù)功能:與 memcpy相同。
注意事項:
src和dest所指內(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;
}

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
{
//自動構造函數(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;
}

10. 內(nèi)存越界
何謂內(nèi)存訪問越界,簡單的說,你向系統(tǒng)申請了一塊內(nèi)存,在使用這塊內(nèi)存的時候,超出了你申請的范圍。
- 訪問到野指針指向的區(qū)域,越界訪問
- 數(shù)組下標越界訪問
- 使用已經(jīng)釋放的內(nèi)存
- 企圖訪問一段釋放的棧空間
- 容易忽略 字符串后面
的'\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指向的??臻g已經(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)存池技術簡介
內(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)存池技術:
內(nèi)存的申請、釋放是低效的,我們只在開始申請一塊大內(nèi)存(不夠繼續(xù)申請),然后每次需要時都從這塊內(nèi)存取出,并標記這塊內(nèi)存是否被使用。釋放時僅僅標記而不真的free,只有內(nèi)存都空閑的時候,才釋放給操作系統(tǒng)。這樣減少了 malloc、free次數(shù),從而提高效率。
13. C語言實現(xiàn)內(nèi)存池
設計思路:
先分配幾個大的連續(xù)內(nèi)存塊(MemoryBlock),每個內(nèi)存塊用鏈表鏈接起來,然后通過一個內(nèi)存池結(jié)構(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é)構下面全是內(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語言內(nèi)存管理的文章就介紹到這了,更多相關C語言內(nèi)存管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

