我知道的只是 “ 肉隨便加 ”和 “ 要加多少加多少 ” 這些詞, ———— 路飛
階段2目標:
此階段開始大量刷題,多多參加編程類競賽,在實戰中鍛煉編程思維和本領,并且要在不斷復習夯實初階的基礎上,刻意地進行編程思維的訓練,學無止境!為了精進編程,可以去學習一切為他服務的課程!

目錄
本章重點:
一、為什么存在動態記憶體分配?
二、動態記憶體函式的介紹
1.malloc函式
2.free函式
3.calloc函式
malloc與calloc的異同:
4.realloc函式 (為了更合理的時候記憶體)
三、常見動態記憶體錯誤
1.對NULL指標的解參考操作
2.對動態開辟空間的越界訪問
3.對非動態開辟記憶體使用free釋放
4.使用free釋放一塊動態開辟記憶體的一部分
5.對同一塊動態記憶體多次釋放
6.動態開辟記憶體忘記釋放(記憶體泄漏)
切記: 動態開辟的空間一定要釋放,并且正確釋放
應用:動態通訊錄(優化版)
本章重點:
1.為什么存在動態記憶體分配
2.動態記憶體函式的介紹
- malloc
- free
- calloc
- realloc
3.常見的動態記憶體錯誤
4.幾個經典的筆試題
5.柔性陣列
一、為什么存在動態記憶體分配?
我們已經掌握的記憶體開辟方式有:
int val = 20;//在堆疊空間上開辟四個位元組
char arr[10] = {0};//在堆疊空間上開辟10個位元組的連續空間
但是,上述開辟空間的方式有兩個特點:
- 空間開辟大小是固定的,
- 陣列在申明的時候,必須指定陣列的長度,它所需要的記憶體在編譯時分配,
但是對于空間的需求,不僅僅是上述的情況,有時候我們需要的空間大小在程式運行的時候才能知道,那陣列的編譯時開辟空間的方式就不能滿足了, 這時候就只能用動態記憶體開辟了,
二、動態記憶體函式的介紹
我們所知道,我們在堆區實作動態記憶體分配,利用malloc 、 calloc 、 realloc 、 free函式去進行相應的動態分配操作,如圖:

1.malloc函式
C語言提供了一個動態記憶體開辟的函式:
void* malloc( size_t size );
這個函式向記憶體申請一塊連續可用的空間,并回傳指向這塊空間的指標,
- 如果開辟成功,則回傳一個指向開辟好空間的指標,
- 如果開辟失敗,則回傳一個NULL指標,因此malloc的回傳值一定要做檢查,
- 回傳值的型別是 void* ,所以malloc函式并不知道開辟空間的型別,具體在使用的時候使用者自己來決定,
- 如果引數 size 為0,malloc的行為是標準是未定義的,取決于編譯器,
解釋:
1.malloc函式的確官方定義是void* 指標,即:萬能型指標,但是我們使用者在使用malloc的時候,需要用具體的型別,來接收,比如:int* 、char*
這樣寫,在解參考取空間中的值得時候,才不會報錯;而如果是void* 去接收,解參考時就會有錯誤,因為編譯器面對void* 該萬能型指標,并不清楚去解參考幾個位元組,
即:在接收時,需要用具體的資料型別來接收malloc的空間,
”錯誤“ 代碼:
int main()
{
//int arr[10] = { 0 };
void* p = malloc(40);
return 0;
}
正確代碼:
int* p = (int*)malloc(40);
2.如果開辟失敗,則回傳一個NULL指標,即:malloc的回傳值一定要做檢查,
#include <stdio.h>
#include<stdlib.h>
int main()
{
//int arr[10] = { 0 };
//申請空間
int* p = (int*)malloc(40);//開辟了40個位元組空間,即:10個int型別空間
//判斷是否 申請失敗
if (p == NULL)
{
printf("申請失敗!");
return -1;
}
//開辟成功了
//(初始化)/(賦值)
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//釋放空間
free(p);
return 0;
}
我們用 *( p + i ) = i;的方式來賦予空間內以初值,用free函式釋放掉指標p維護的10個int型別的空間,而我們除錯發現,free掉p之后,僅僅是那10個空間被釋放掉了,而指標p仍舊是指向那塊空間的地址,構成了一個野指標,所以以后就很危險,需要將p=NULL;讓程式更安全,
#include <stdio.h>
#include<stdlib.h>
int main()
{
//int arr[10] = { 0 };
//申請空間
int* p = (int*)malloc(40);//開辟了40個位元組空間,即:10個int型別空間
//判斷是否 申請失敗
if (p == NULL)
{
printf("申請失敗!");
return -1;
}
//開辟成功了
//(初始化)/(賦值)
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//釋放空間
free(p);
p = NULL;
return 0;
}
2.free函式
C語言提供了另外一個函式free,專門是用來做動態記憶體的釋放和回收的,函式原型如下:
void free( void *memblock );
free函式用來釋放動態開辟的記憶體,
- 如果引數 ptr 指向的空間不是動態開辟的,那free函式的行為是未定義的,
- 如果引數 ptr 是NULL指標,則函式什么事都不做,
解釋:
1.如果引數 ptr 指向的空間不是動態開辟的,那free函式的行為是未定義的,
2.如果引數 ptr 是NULL指標,則函式什么事都不做,
假如 p指標并不是malloc來的,那么對于free(p);在C語言中是未定義的,即:free也不知道該怎么辦了,
總結:
除非 p 本身等于NULL,否則free以后不會等于NULL,因為free不對指標的值做任何操作,而只是試圖改變指標指向的一片連續的存盤器空間的狀態,如果這片存盤器空間是malloc或其它兼容方式(例如POSIX庫函式strdup)分配過來的,那么會釋放這片空間,釋放的空間可以之后再次被分配,如果指標本來就等于NULL,則呼叫free不會有任何作用,除以上兩種情況外(包括再次free已經被free過的非空指標),free的行為是未定義的,比較有可能的是free這個指標行程在某個時刻突然莫名其妙地崩潰,
3.calloc函式
C語言還提供了一個函式叫 calloc , calloc 函式也用來動態記憶體分配,原型如下:
void* calloc( size_t num , size_t size );
函式的功能是為 num 個大小為 size 的元素開辟一塊空間,并且把空間的每個位元組初始化為0,
與函式 malloc 的區別只在于 calloc 會在回傳地址之前把申請的空間的每個位元組初始化為全0, 舉個例子:
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* p = calloc(10, sizeof(int));
//errno是存盤錯誤資訊
//用strerror函式回傳 錯誤碼所對應的錯誤資訊
if (p == NULL)
{
printf("%s\n", strerror(errno));
return -1;
}
//開辟成功了
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//釋放空間
free(p);
p = NULL;
return 0;
}
可能對于malloc與calloc有些疑問?什么情況下會開辟失敗?? 當然,記憶體空間不是無限大的,如果你開辟的過多,就會開辟失敗,如:

malloc與calloc的異同:
同:都可以動態分配記憶體空間
異:
malloc函式僅僅是申請記憶體空間,并且回傳起始地址,并不去初始化;而calloc函式既申請記憶體空間,并且回傳起始地址,又去初始化每個位元組為0,
總結:
在應用中,想要初始化就應用calloc,不想初始化就用malloc即可,
4.realloc函式 (為了更合理的時候記憶體)
(追加增容)
realloc函式的出現讓動態記憶體管理更加靈活,
有時會我們發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時候記憶體,我們一定會對記憶體的大小做靈活的調整,那 realloc 函式就可以做到對動態開辟記憶體大小的調整, 函式原型如下:
void* realloc ( void* ptr, size_t size );
- ptr 是要調整的記憶體地址,
- size 是調整之后新大小空間,
- 回傳值為調整之后的記憶體起始位置,( 起初整個記憶體的開頭 )
- 這個函式調整原記憶體空間大小的基礎上,還會將原來記憶體中的資料移動到 新 的空間,
- realloc在調整記憶體空間的是存在兩種情況:
- 情況1:原有空間之后有足夠大的空間
- 情況2:原有空間之后沒有足夠大的空間
情況1 當是情況1 的時候,要擴展記憶體就直接原有記憶體之后直接追加空間,原來空間的資料不發生變化,
情況2 當是情況2 的時候,原有空間之后沒有足夠多的空間時,擴展的方法是:在堆空間上另找一個合適大小的連續空間來使用,這樣函式回傳的是一個新的記憶體地址,
由于上述的兩種情況,realloc函式的使用就要注意一些,
原有空間之后有足夠大的空間

原有空間之后沒有足夠大的空間

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
//calloc,申請空間,并初始化為0
int* p = calloc(10, sizeof(int));
//errno是存盤錯誤資訊
//用strerror函式回傳 錯誤碼所對應的錯誤資訊
if (p == NULL)
{
printf("%s\n", strerror(errno));
return -1;
}
//開辟成功了
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//空間不夠,增加空間至 20個int
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
//以防空指標,造成越界訪問,程式崩潰
else
{
return -1;
}
//給新開辟的空間賦值
for (i = 10; i < 20; i++)
{
*(p + i) = i;
}
//列印
for (i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
//釋放空間
free(p);
p = NULL;
return 0;
}
三、常見動態記憶體錯誤
1.對NULL指標的解參考操作
錯誤寫法:
int* p = (int*)malloc(20);
*p = 20;//直接這樣寫,是有風險的!!
free(p);
改寫:
int* p = (int*)malloc(20);
if (p == NULL)
{
return -1;
}
*p = 20;//直接這樣寫,是有風險的!!
free(p);
2.對動態開辟空間的越界訪問
錯誤寫法:
int main()
{
int* p = (int*)malloc(200);
if (p == NULL)
{
return -1;
}
int i = 0;
for (i = 0; i <= 80; i++)
{
*(p + i) = i;//當i是10的時候越界訪問
}
free(p);
p = NULL;
return 0;
}
改寫:
int main()
{
//此處的200,不是200個空間,而是200個位元組,對應到int型別只是50個空間
//而 訪問的是0 ~ 80,造成了越界訪問
int* p = (int*)malloc(200);
if (p == NULL)
{
return -1;
}
int i = 0;
for (i = 0; i <= 50/*80*/; i++)
{
*(p + i) = i;//當i是10的時候越界訪問
}
free(p);
p = NULL;
return 0;
}
3.對非動態開辟記憶體使用free釋放
錯誤代碼:
int main()
{
int a = 10;
int* p = &a;
free(p);
p = NULL;
return 0;
}
該錯誤在于,變數a在記憶體的堆疊區,而free是釋放動態記憶體的,即:對堆區的空間才有用,此時程式會崩潰,
4.使用free釋放一塊動態開辟記憶體的一部分
錯誤代碼:
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return -1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*p++ = i;
}
free(p);
p = NULL;
return 0;
}
錯誤原因:
p最后指向的不再是起始地址,而free釋放就要釋放全部( free的脾氣 ),
5.對同一塊動態記憶體多次釋放
錯誤代碼:
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return -1;
}
//使用
//...
//釋放
free(p);
free(p);
p = NULL;
return 0;
}
對于不屬于自己的指標( 已經釋放過了,p不再維護該動態分配的空間了 ),再次進行free(p),程式就會崩潰; 請思考,這樣寫對不對呢?
//釋放
free(p);
p = NULL;
free(p);
p = NULL;
這樣寫,是對的,因為p已經置NULL了,而對于NULL,我們知道,free(BULL);是沒錯的,只不過是沒有用處的寫法罷了!
6.動態開辟記憶體忘記釋放(記憶體泄漏)
錯誤代碼:
//動態開辟的記憶體忘記釋放
//在堆區申請的空間,有2種方式可以回收
//1.主動free
//2.程式結束時,作業系統會自動回收
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return -1;
}
//使用
//...
//忘記釋放了
return 0;
}
錯誤原因:
我們在堆區開辟的記憶體,一直未釋放,就會一直存在與堆區,造成記憶體被占用,對應到后端程式就是,運行變卡;這樣一塊記憶體,我們不能夠對他進行使用,也沒有去釋放掉,就造成了 “ 記憶體泄漏 ”,

忘記釋放不再使用的動態開辟的空間會造成記憶體泄漏,
切記: 動態開辟的空間一定要釋放,并且正確釋放
應用:動態通訊錄(優化版)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/303045.html
標籤:其他
