使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管
前言
我們已經(jīng)掌握的內(nèi)存開辟的方法有兩種
int a = 10; //在??臻g上開辟4個字節(jié)的空間
int a[10] = {0}; //在棧空間上開辟40個字節(jié)的連續(xù)空間
這些開辟方式都有兩個共同的特點(diǎn):
1.空間開辟大小是固定的
2.數(shù)組在申明的時候,必須指定數(shù)組的長度,它需要的內(nèi)存在編譯的時候分配
我們?yōu)槭裁匆獙崿F(xiàn)動態(tài)管理內(nèi)存呢,這又什么作用呢?
我們對于空間的需求不僅僅只是上面兩種,有時候我們到底需要多少空間,需要運(yùn)行之后才能知道,這個時候就需要動態(tài)開辟內(nèi)存空間,即動態(tài)內(nèi)存函數(shù)就誕生了!
動態(tài)內(nèi)存函數(shù)有那些?
1.malloc和free
2.calloc
3.realloc
malloc和free
malloc是C語言提供的一個動態(tài)內(nèi)存開辟的函數(shù):

這個函數(shù)向內(nèi)存申請一塊連續(xù)可用的空間,并返回指向這塊空間的指針。
1.如果開辟成功,則返回一個指向開辟好空間的指針。
2.如果開辟失敗,則返回一個 NULL 指針,因此 malloc 的返回值一定要做檢查。
3.返回值的類型是 void* ,所以 malloc 函數(shù)并不知道開辟空間的類型,具體在使用的時候使用者自己來決定。
4.如果參數(shù) size 為 0 , malloc 的行為是標(biāo)準(zhǔn)是未定義的,取決于編譯器。
void*的返回類型,使用的時候根據(jù)情況強(qiáng)制類型轉(zhuǎn)換
C語言還提供free函數(shù),專門是用于做動態(tài)內(nèi)存的釋放和回收的,函數(shù)原型如下:

free函數(shù)是用來釋放動態(tài)開辟的內(nèi)存:
1.如果參數(shù) ptr 指向的空間不是動態(tài)開辟的,那free函數(shù)的行為是未定義的。(會報錯)
2.如果參數(shù) ptr 是NULL指針,則函數(shù)什么事都不做。
圖文演示:
頭文件要加上 malloc.h

代碼演示:
int main()
{
int num = 0;
scanf("%d", &num);
//int arr[num] = { 0 }; num 在 [] 中
//VS 不支持這樣,但是可以使用動態(tài)內(nèi)存函數(shù),實現(xiàn)動態(tài)數(shù)組
int* ptr = (int*)malloc(sizeof(int) * num);
if (NULL == ptr) {//進(jìn)行判斷是否創(chuàng)建成功
perror("malloc::ptr");
}
else {
for (int i = 0; i < 10; i++) {
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", *(ptr + i));
}
free(ptr); //使用free函數(shù)釋放動態(tài)申請的ptr
ptr = NULL; //將ptr free之后,置為NULL,防止野指針非法訪問
}
return 0;
}而且malloc函數(shù)創(chuàng)建的空間不會進(jìn)行初始化,里面存放的是隨機(jī)值,如圖

1.2 calloc
calloc函數(shù)也是C語言提供的,用來動態(tài)內(nèi)存分配,原型如下:

calloc函數(shù)介紹:
1.函數(shù)的功能是為 num 個大小為 size 的元素開辟一塊空間,并且把空間的每個字節(jié)初始化為 0。
2.與函數(shù) malloc 的區(qū)別只在于 calloc 會在返回地址之前把申請的空間的每個字節(jié)初始化為全 0。
實操圖文分析:

代碼演示:
int main()
{
int num = 0;
int* ptr = (int*)calloc(10, sizeof(int));//使用calloc函數(shù)
for (int i = 0; i < 10; i++) {
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", *(ptr + i));
}
free(ptr);//free 動態(tài)申請的ptr
ptr = NULL;//置為NULL,防止野指針越界訪問
return 0;
}對于calloc動態(tài)申請的空間是否每一個字節(jié)都變?yōu)?呢?我們來看下圖

這也是calloc和malloc函數(shù)的最大的區(qū)別,是否自動初始化,前者有,后者無
realloc
realloc也是C語言提供的動態(tài)內(nèi)存申請函數(shù),使得動態(tài)內(nèi)存管理更加靈活。
本質(zhì)是可以對已經(jīng)動態(tài)申請過的空間進(jìn)行增容,是更加靈活的。
有時會我們發(fā)現(xiàn)過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時 候內(nèi)存,我們一定會對內(nèi)存的大小做靈活的調(diào)整。那 realloc 函數(shù)就可以做到對動態(tài)開辟內(nèi)存大小 的調(diào)整。
函數(shù)原型如下,并對兩個形參ptr和size進(jìn)行分析:

如上圖:
1.ptr可以為NULL,相當(dāng)于malloc一個新的空間,ptr是要調(diào)整的內(nèi)存地址
2.size同樣可以為0,則返回值取決于特定的庫實現(xiàn):它可能是空指針,也可能是不應(yīng)取消引用的其他位置。size是調(diào)整之后的大小
3.返回值為調(diào)整之后的內(nèi)存起始位置。
4.這個函數(shù)調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,還會將原來的數(shù)據(jù)移動到新空間。
realloc調(diào)整內(nèi)存空間的時候有兩種情況:
第一種情況:當(dāng)原有空間之后的內(nèi)存空間足夠的時候
第二種情況:當(dāng)原有空間之后的內(nèi)存空間不夠時
如圖所示:

因為這兩種情況是隨機(jī)發(fā)生的,不能控制必須使用哪一種,所以我們就要小心一個事情,不要用原來動態(tài)開辟的變量ptr來直接接收realloc,應(yīng)該創(chuàng)建臨時變量接收,先判空,之后再賦值給ptr
代碼圖示:

可以自行測試:
int main()
{
int* p = (int*)malloc(sizeof(int)*10);
if (p == NULL) {
perror("malloc::p");
}
else {
printf("%p\n", p);
}
int* ptr = (int*)realloc(p, sizeof(int) * 20);//創(chuàng)建臨時變量
//如果使用 int* p = (int*)realloc(p,....這樣的話如果創(chuàng)建失敗,返回NULL,
//這樣的話p的內(nèi)容就沒有了,所以創(chuàng)建臨時變量ptr,然后下面判空之后可以交換
if (NULL == ptr) {
perror("realloc::ptr");
}
else {
p = ptr;
ptr = NULL;
printf("%p\n", p);
}
free(p);
p = NULL;
return 0;
}常見動態(tài)內(nèi)存錯誤(案例分析)
對于NULL指針的解引用操作
意思就是要學(xué)會使用動態(tài)內(nèi)存函數(shù)的時候嗎,要進(jìn)行判空,不然誰知道有沒有問題NULL
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
*p = 10;//這個時候誰知道p是不是NULL,如果是NULL,那么這就是非法訪問,是錯誤
free(p);
return 0;
}對動態(tài)開辟空間的越界訪問
就是說,開辟多少空間就是多少空間,不能越過這個字節(jié)數(shù)的界限訪問空間外的地址
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
if (NULL == p) {
perror("malloc::p");
}
else {
for (int i = 0; i < 100; i++) {
*(p + i) = i + 1;//當(dāng)i等于10的時候就開始越界訪問
}
for (int i = 0; i < 11; i++) {
printf("%d ", *(p + i));
}
free(p);
p = NULL;
}
return 0;
}和數(shù)組一樣,不要越界,不需要多想什么額外的東西
對非動態(tài)開辟內(nèi)存使用free釋放
free可以放置NULL進(jìn)去,不會報錯,但是不能放非動態(tài)開辟的內(nèi)存,會報錯
圖示分析free函數(shù):

代碼演示:
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
int a = 10;
free(&a);//非動態(tài)內(nèi)存開辟的,會報錯
//free(NULL); //沒有什么反應(yīng),程序正常
return 0;
}使用free釋放了動態(tài)開辟內(nèi)存的一部分
就是說如果動態(tài)開辟內(nèi)存之后的p指針的位置發(fā)生改變的話再去釋放free(p)只是釋放一部分
代碼演示:
//舉例
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
p++;
free(p);//這個的時候p向右移動一個整型字節(jié)空間,再進(jìn)行釋放,那么先前那個空間就沒被釋放
return 0;
}對同一塊動態(tài)內(nèi)存進(jìn)行多次釋放
多次釋放會報錯的
圖示:

動態(tài)開辟空間忘記釋放(內(nèi)存泄漏)
所以我們要養(yǎng)成當(dāng)一個動態(tài)空間不用的時候就free他,放置內(nèi)存泄露
代碼演示:
int main()
{
//test();
while (1) {
malloc(1);//一直申請就是不釋放
}
}練習(xí)題
第一個
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
//改為傳遞地址就可以或者就是用str接收
//str= GetMemory(str);//實際上用臨時變量接收更好
strcpy(str, "hello world");
printf(str);
//用完釋放
//free(str);
//str=NULL;
}1.傳值操作,就算p申請了空間也不會使得str發(fā)生改變,所以str依舊是NULL,不能有strcpy
2.內(nèi)存泄漏, GetMemory(str);未釋放p的空間
第二個
char *GetMemory(void)
{
//修改為:
//static char p[] = "hello world";
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}典型的返回棧地址問題,p數(shù)組是局部變量 ,確實是返回了p的地址給str,但是GetMemory函數(shù)結(jié)束之后,數(shù)組p的空間就沒有,再訪問p的地址(printf(str))就會非法訪問
第三個
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
//修改為:free(str);
//str=NULL;
}沒有釋放str動態(tài)開辟的空間,沒有free(str),str=NULL
第四個
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
//修改為:free(str);
//str=NULL;
}在使用str之前就釋放了str申請的空間,釋放之后str!=NULL,保留原來地址,str這個時候已經(jīng)是野指針了(因為沒有了對相應(yīng)空間的訪問權(quán)限),之后確實是輸出了world,但是從if語句就已經(jīng)錯誤了,置為str=NULL 就可以了
總結(jié)
本文主要是對于malloc、calloc、realloc、free函數(shù)的介紹和使用細(xì)節(jié)的說明,還有一些關(guān)于動態(tài)內(nèi)存管理的函數(shù),學(xué)會了這些,對于以后數(shù)據(jù)結(jié)構(gòu)的內(nèi)容會更加得心應(yīng)手,所以希望大家能多多支持,接下來,下一章,我們跟大家講解一下,文件管理的內(nèi)容。學(xué)會了就可以更新通訊錄啦?。?!
以上就是使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管的詳細(xì)內(nèi)容,更多關(guān)于c語言實現(xiàn)動態(tài)內(nèi)存管的資料請關(guān)注腳本之家其它相關(guān)文章!
- 深入了解C語言的動態(tài)內(nèi)存管理
- 詳解C語言中動態(tài)內(nèi)存管理及柔性數(shù)組的使用
- C語言動態(tài)內(nèi)存的分配最全面分析
- 一文帶你搞懂C語言動態(tài)內(nèi)存管理
- 詳解C語言中的動態(tài)內(nèi)存管理
- C語言動態(tài)內(nèi)存分配圖文講解
- 一文帶你了解C語言中的動態(tài)內(nèi)存管理函數(shù)
- C語言動態(tài)內(nèi)存管理的原理及實現(xiàn)方法
- 詳解C語言中動態(tài)內(nèi)存管理
- C語言中常見的六種動態(tài)內(nèi)存錯誤總結(jié)
- 一文解析C語言中動態(tài)內(nèi)存管理
- C語言動態(tài)內(nèi)存管理的實現(xiàn)示例
相關(guān)文章
C語言中數(shù)據(jù)結(jié)構(gòu)之鏈表歸并排序?qū)嵗a
這篇文章主要介紹了C語言中數(shù)據(jù)結(jié)構(gòu)之鏈表歸并排序?qū)嵗a的相關(guān)資料,需要的朋友可以參考下2017-05-05
C語言數(shù)據(jù)結(jié)構(gòu)之中綴樹轉(zhuǎn)后綴樹的實例
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之中綴樹轉(zhuǎn)后綴樹的實例的相關(guān)資料,需要的朋友可以參考下2017-08-08

