文章目錄
- 一、為什么會有動態內存管理
- 二、申請內存函數
- 1、malloc
- 2、free
- 3、calloc
- 4、realloc
- 三、常見的動態內存的錯誤
- 四、練習
一、為什么會有動態內存管理
1.我們一般的開辟空間方式:
int a = 0;//申請4個字節空間
int arr[10] = { 0 };//申請40個字節空間
2.這樣開辟空間的特點
(1)申請的空間大小是固定的
(2)像數組那樣一開始就要確定大小,一旦確定大小就不能改變了
3.動態內存
對于程序來說上述的內存申請是不能滿足 因此為了能夠對內存進行調整,C語言引入了動態內存開辟,讓程序員自己可以申請和釋放空間,就比較靈活了。
二、申請內存函數
以下動態申請的內存都是向堆區申請的,
并且都申請內存的函數和釋放函數都包含在頭文件 :#include<stdlib.h>
1、malloc
(1)返回類型和參數:
void* malloc(size_t size);//返回類型為 void* ,參數為正整數單位是字節
因為返回類型為 void*,所以在malloc函數是不知道我們想要申請什么類型的空間,面對這種情況我們要將返回的類型進行強制類型轉換,這樣就能返回我們需要的類型了,參數是申請的大小
(2)作用:
向內存申請一塊連續的空間,并返回指向這塊空間的的指針
(3)注意:
a.在申請完之后我們還要判斷是否成功申請
當申請失敗時會返回NULL
當申請成功后就返回指向這塊空間的的指針,這樣可以正常使用這塊空間了
b.如果參數 size 為0,malloc的行為是標準是未定義的,取決于編譯器
(4)使用:
int main() {int* p = (int*)malloc(sizeof(int) * 10);//向內存申請40個字節空間if (p == NULL)//判斷是否申請成功return 1;//失敗直接放回for (int i = 0; i < 10; i++)//成功就正常使用*(p + i) = i;for (int i = 0; i < 10; i++)//打印printf("%d ", *(p + i));return 0;
}
運行結果:
2、free
(1)返回類型和參數:
void free( void* p)//參數為向動態內存申請的空間的指針,返回類型為空
(2)作用:
專門進行對動態內存的釋放和回收
(3)注意:
a.當釋放的內存不是動態開辟的,這是free未定義的
b.當p是NULL時,free函數什么事都不發生
c.當我們將p指向的空間釋放后,要將p置空,不然p就成野指針了
(4)使用
在我們上一個代碼中,并未對malloc函數開辟的空間進行釋放,其實這是不對的,這會造成內存泄漏。 那么我們就來用一下free函數吧
int main() {int* p = (int*)malloc(sizeof(int) * 10);//向內存申請40個字節空間if (p == NULL)//判斷是否申請成功return 1;//失敗直接放回for (int i = 0; i < 10; i++)//成功就正常使用*(p + i) = i;for (int i = 0; i < 10; i++)//打印printf("%d ", *(p + i));free(p);//釋放p = NULL;//及時置空,防止出現野指針return 0;
}
這樣代碼才算完整。
3、calloc
(1)返回類型和參數:
void *calloc(size_t n,size_t size);
返回類型為 void* ,所以和malloc一樣想要什么類型的空間就進行強制轉換類型即可,第一個參數為申請的個數,第二個參數為申請的類型的空間大小(單位為字節)
(2)作用:
申請一塊連續的空間,并將空間的內容全部初始化為0,然后返回指向這塊空間的指針
(3)注意:
a.在申請完之后我們還要判斷是否成功申請
當申請失敗時會返回NULL
當申請成功后就返回指向這塊空間的的指針,這樣可以正常使用這塊空間了
b.如果參數 size 為0,malloc的行為是標準是未定義的,取決于編譯器
c.與malloc的區別就是calloc會將申請的空間初始化,這樣使用時更方便
(4)使用:
int main() {int* p = (int*)calloc(10,sizeof(int));//申請if (p == NULL) {//判斷printf("NULL");return 1;}//使用for (int i = 0; i < 10; i++)*(p + i) = i;for (int i = 0; i < 10; i++)printf("%d ", *(p + i));free(p);//同樣的釋放空間p = NULL;//置空return 0;
}
運行結果:
4、realloc
(1)返回類型和參數:
void *realloc(void * p ,size_t size);
返回類型為 void* 所以和calloc一樣想要什么類型就強制類型轉換,第一個參數p為指向想要的改變的空間的 指針,第二參數為改變的大小(單位為字節)
(2)作用:
在原來的動態內存的空間上增大或者縮小,并返回改變后指向新的空間的指針
(3)注意:
a.開辟失敗返回空指針
b.開辟的新的空間時有兩種開辟的方式,第一種是我們原本的空間后面有足夠的空間,那樣直接在原來的空間后面擴容,第二種是我們原本的空間后面沒有足夠的空間,那樣的話,系統就會在內存上找一塊適合的空間重新開辟,并將原來空間的內容復雜過去,再將原來的空間銷毀。
由于開辟的情況有兩種,所以我們使用時要注意開辟空間失敗這種情況
如:我們使用了第二種情況開辟空間、并直接用原來的指針去接收指向開辟的空間的指針,如果開辟失敗的話返回NULL,這導致原來的數據會丟失。
所以為了解決這個隱患,我們可以重新定義一個指針來接收,判斷不為空再將該指針賦給原來的指針。
圖:
(4)使用:
int main() {//先用動態內存開辟一個空間int* p = (int*)calloc(10, sizeof(int));if (p == NULL)return 1;for (int i = 0; i < 10; i++)*(p + i) = i;for (int i = 0; i < 10; i++)printf("%d ", *(p + i));printf("\n");//使用realloc增加空間int* pp = (int*)realloc(p, sizeof(int) * 15);//再申請一個指針變量來接收if (pp == NULL)return 1;elsep = pp;//不為空再賦給原來的指針for (int i = 10; i < 15; i++)*(p + i) = i;for (int i = 10; i < 15; i++)printf("%d ", *(p + i));free(p);//最后不要忘記了釋放并置空p = NULL;return 0;
}
運行結果:
三、常見的動態內存的錯誤
1、對NULL指針的解引用操作
void test(){int *p = (int *)malloc(INT_MAX/4);//開辟空間//這里我們不知道是否開辟成功*p = 20;//如果p的值是NULL,就會有問題free(p);p=NULL;}
這里的問題是不知道p為不為NULL
改:
void test(){int *p = (int *)malloc(INT_MAX/4);//開辟空間if(p!=NULL)*p = 20;free(p);p=NULL;}
2 、對動態開辟空間的越界訪問
void test(){int i = 0;int *p = (int *)malloc(10*sizeof(int));if(NULL == p){exit(EXIT_FAILURE);}for(i=0; i<=10; i++){*(p+i) = i;//當i是10的時候越界訪問}free(p);p=NULL;}
改:
void test(){int i = 0;int *p = (int *)malloc(10*sizeof(int));if(NULL == p){exit(EXIT_FAILURE);}for(i=0; i<10; i++)//當我們改成<10之后i就不會到10,也就不會越界了{*(p+i) = i;}free(p);p=NULL;}
3 、對非動態開辟內存使用free釋放
void test(){int a = 10;int *p = &a;free(p);//ok?}
這種行為在free函數中未定義,最好不要使用
4 、使用free釋放?塊動態開辟內存的?部分
void test(){int *p = (int *)malloc(100);p++;free(p);//p不再指向動態內存的起始位置}
這種行為會導致內存泄漏
5 、對同?塊動態內存多次釋放
void test(){int *p = (int *)malloc(100);free(p);free(p);//重復釋放}
當出現這種情況程序會崩掉
6、 動態開辟內存忘記釋放(內存泄漏)
void test(){int *p = (int *)malloc(100);if(NULL != p){*p = 20;}}
int main(){test();while(1);}
這種未對申請的空間釋放,會導致內存泄漏
四、練習
1、請問運行Test 函數會有什么樣的結果?
void GetMemory(char *p){p = (char *)malloc(100);}
void Test(void){char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);}
運行結果:
什么都沒有輸出,這是為什么呢?
這是因為調用GetMemory函數時使用的是傳值調用,p雖然申請到了空間,但是并沒有改變str的值,所以str=NULL,所以什么內容都沒有打印,
改;我們可以將傳值調用改為傳址調用
如:
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);
}
int main() {Test();return 0;
}
運行結果:
成功打印
2、請問運行Test 函數會有什么樣的結果?
char *GetMemory(void){char p[] = "hello world";return p;}
void Test(void){char *str = NULL;str = GetMemory();printf(str);}
運行結果:
出現了隨機值,這是為什么呢?
因為p是在棧區創建,當函數結束后該p指向的空間也會銷毀然后返還給內存,此時將p傳給str之后,str就會變成野指針,它指向的空間打印出來的就是隨機值了
改:我們可以用動態內存
如:
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);
}
int main() {Test();return 0;
}
運行結果:
3、請問運行Test 函數會有什么樣的結果?
void GetMemory(char **p, int num){*p = (char *)malloc(num);}
void Test(void){char *str = NULL;GetMemory(&str, 100);strcpy(str, "hello");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;
}
int main() {Test();return 0;
}
4、請問運行Test 函數會有什么樣的結果?
void Test(void){char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, "world");printf(str);}}
運行結果:
雖然結果正確,但是其實是有問題的
1.因為str存儲的地址不會改變,應該手動置空,但是它釋放空間后沒有置空
2.使用釋放的空間(這塊空間已經還給系統了)
3.為什么還能打印world呢?那是因為該塊空間沒有被覆蓋,world還在那里
我們可以試試再他前面再申請一次動態內存看看
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");char* str1 = (char*)malloc(100);printf(str);}
}
int main() {Test();return 0;
}
運行結果:
world被覆蓋了,就打印不出了
改:我們可以釋放后置空,就不會出現這種情況了
void Test(void){char *str = (char *) malloc(100);strcpy(str, "hello");free(str);str=NULL;if(str != NULL){strcpy(str, "world");printf(str);}}
以上就是我的分享了,如果有什么錯誤,歡迎在評論區留言。
最后,謝謝大家的觀看!