一、為什么會有動態(tài)內(nèi)存管理
1.我們一般的開辟空間方式:
int a = 0;//申請4個(gè)字節(jié)空間
int arr[10] = { 0 };//申請40個(gè)字節(jié)空間
2.這樣開辟空間的特點(diǎn)
(1)申請的空間大小是固定的
(2)像數(shù)組那樣一開始就要確定大小,一旦確定大小就不能改變了
3.動態(tài)內(nèi)存
對于程序來說上述的內(nèi)存申請是不能滿足 因此為了能夠?qū)?nèi)存進(jìn)行調(diào)整,C語言引入了動態(tài)內(nèi)存開辟,讓程序員自己可以申請和釋放空間,就比較靈活了。
二、申請內(nèi)存函數(shù)
以下動態(tài)申請的內(nèi)存都是向堆區(qū)申請的,
并且都申請內(nèi)存的函數(shù)和釋放函數(shù)都包含在頭文件 :#include<stdlib.h>
1、malloc
(1)返回類型和參數(shù):
void* malloc(size_t size);//返回類型為 void* ,參數(shù)為正整數(shù)單位是字節(jié)
因?yàn)榉祷仡愋蜑?void*,所以在malloc函數(shù)是不知道我們想要申請什么類型的空間,面對這種情況我們要將返回的類型進(jìn)行強(qiáng)制類型轉(zhuǎn)換,這樣就能返回我們需要的類型了,參數(shù)是申請的大小
(2)作用:
向內(nèi)存申請一塊連續(xù)的空間,并返回指向這塊空間的的指針
(3)注意:
a.在申請完之后我們還要判斷是否成功申請
當(dāng)申請失敗時(shí)會返回NULL
當(dāng)申請成功后就返回指向這塊空間的的指針,這樣可以正常使用這塊空間了
b.如果參數(shù) size 為0,malloc的行為是標(biāo)準(zhǔn)是未定義的,取決于編譯器
(4)使用:
int main() {
int* p = (int*)malloc(sizeof(int) * 10);//向內(nèi)存申請40個(gè)字節(jié)空間
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;
}
運(yùn)行結(jié)果:
2、free
(1)返回類型和參數(shù):
void free( void* p)//參數(shù)為向動態(tài)內(nèi)存申請的空間的指針,返回類型為空
(2)作用:
專門進(jìn)行對動態(tài)內(nèi)存的釋放和回收
(3)注意:
a.當(dāng)釋放的內(nèi)存不是動態(tài)開辟的,這是free未定義的
b.當(dāng)p是NULL時(shí),free函數(shù)什么事都不發(fā)生
c.當(dāng)我們將p指向的空間釋放后,要將p置空,不然p就成野指針了
(4)使用
在我們上一個(gè)代碼中,并未對malloc函數(shù)開辟的空間進(jìn)行釋放,其實(shí)這是不對的,這會造成內(nèi)存泄漏。 那么我們就來用一下free函數(shù)吧
int main() {
int* p = (int*)malloc(sizeof(int) * 10);//向內(nèi)存申請40個(gè)字節(jié)空間
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;//及時(shí)置空,防止出現(xiàn)野指針
return 0;
}
這樣代碼才算完整。
3、calloc
(1)返回類型和參數(shù):
void *calloc(size_t n,size_t size);
返回類型為 void* ,所以和malloc一樣想要什么類型的空間就進(jìn)行強(qiáng)制轉(zhuǎn)換類型即可,第一個(gè)參數(shù)為申請的個(gè)數(shù),第二個(gè)參數(shù)為申請的類型的空間大?。▎挝粸樽止?jié))
(2)作用:
申請一塊連續(xù)的空間,并將空間的內(nèi)容全部初始化為0,然后返回指向這塊空間的指針
(3)注意:
a.在申請完之后我們還要判斷是否成功申請
當(dāng)申請失敗時(shí)會返回NULL
當(dāng)申請成功后就返回指向這塊空間的的指針,這樣可以正常使用這塊空間了
b.如果參數(shù) size 為0,malloc的行為是標(biāo)準(zhǔn)是未定義的,取決于編譯器
c.與malloc的區(qū)別就是calloc會將申請的空間初始化,這樣使用時(shí)更方便
(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;
}
運(yùn)行結(jié)果:
4、realloc
(1)返回類型和參數(shù):
void *realloc(void * p ,size_t size);
返回類型為 void* 所以和calloc一樣想要什么類型就強(qiáng)制類型轉(zhuǎn)換,第一個(gè)參數(shù)p為指向想要的改變的空間的 指針,第二參數(shù)為改變的大?。▎挝粸樽止?jié))
(2)作用:
在原來的動態(tài)內(nèi)存的空間上增大或者縮小,并返回改變后指向新的空間的指針
(3)注意:
a.開辟失敗返回空指針
b.開辟的新的空間時(shí)有兩種開辟的方式,第一種是我們原本的空間后面有足夠的空間,那樣直接在原來的空間后面擴(kuò)容,第二種是我們原本的空間后面沒有足夠的空間,那樣的話,系統(tǒng)就會在內(nèi)存上找一塊適合的空間重新開辟,并將原來空間的內(nèi)容復(fù)雜過去,再將原來的空間銷毀。
由于開辟的情況有兩種,所以我們使用時(shí)要注意開辟空間失敗這種情況
如:我們使用了第二種情況開辟空間、并直接用原來的指針去接收指向開辟的空間的指針,如果開辟失敗的話返回NULL,這導(dǎo)致原來的數(shù)據(jù)會丟失。
所以為了解決這個(gè)隱患,我們可以重新定義一個(gè)指針來接收,判斷不為空再將該指針賦給原來的指針。
圖:
(4)使用:
int main() {
//先用動態(tài)內(nèi)存開辟一個(gè)空間
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);//再申請一個(gè)指針變量來接收
if (pp == NULL)
return 1;
else
p = 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;
}
運(yùn)行結(jié)果:
三、常見的動態(tài)內(nèi)存的錯誤
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 、對動態(tài)開辟空間的越界訪問
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;//當(dāng)i是10的時(shí)候越界訪問
}
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++)//當(dāng)我們改成<10之后i就不會到10,也就不會越界了
{
*(p+i) = i;
}
free(p);
p=NULL;
}
3 、對非動態(tài)開辟內(nèi)存使用free釋放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
這種行為在free函數(shù)中未定義,最好不要使用
4 、使用free釋放?塊動態(tài)開辟內(nèi)存的?部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向動態(tài)內(nèi)存的起始位置
}
這種行為會導(dǎo)致內(nèi)存泄漏
5 、對同?塊動態(tài)內(nèi)存多次釋放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重復(fù)釋放
}
當(dāng)出現(xiàn)這種情況程序會崩掉
6、 動態(tài)開辟內(nèi)存忘記釋放(內(nèi)存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
這種未對申請的空間釋放,會導(dǎo)致內(nèi)存泄漏
四、練習(xí)
1、請問運(yùn)行Test 函數(shù)會有什么樣的結(jié)果?
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
運(yùn)行結(jié)果:
什么都沒有輸出,這是為什么呢?
這是因?yàn)檎{(diào)用GetMemory函數(shù)時(shí)使用的是傳值調(diào)用,p雖然申請到了空間,但是并沒有改變str的值,所以str=NULL,所以什么內(nèi)容都沒有打印,
改;我們可以將傳值調(diào)用改為傳址調(diào)用
如:
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;
}
運(yùn)行結(jié)果:
成功打印
2、請問運(yùn)行Test 函數(shù)會有什么樣的結(jié)果?
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
運(yùn)行結(jié)果:
出現(xiàn)了隨機(jī)值,這是為什么呢?
因?yàn)閜是在棧區(qū)創(chuàng)建,當(dāng)函數(shù)結(jié)束后該p指向的空間也會銷毀然后返還給內(nèi)存,此時(shí)將p傳給str之后,str就會變成野指針,它指向的空間打印出來的就是隨機(jī)值了
改:我們可以用動態(tài)內(nèi)存
如:
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;
}
運(yùn)行結(jié)果:
3、請問運(yùn)行Test 函數(shù)會有什么樣的結(jié)果?
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
運(yùn)行結(jié)果:
輸出正確。但是這里的問題時(shí)沒有釋放動態(tài)內(nèi)存
改:
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、請問運(yùn)行Test 函數(shù)會有什么樣的結(jié)果?
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
運(yùn)行結(jié)果:
雖然結(jié)果正確,但是其實(shí)是有問題的
1.因?yàn)閟tr存儲的地址不會改變,應(yīng)該手動置空,但是它釋放空間后沒有置空
2.使用釋放的空間(這塊空間已經(jīng)還給系統(tǒng)了)
3.為什么還能打印world呢?那是因?yàn)樵搲K空間沒有被覆蓋,world還在那里
我們可以試試再他前面再申請一次動態(tài)內(nèi)存看看
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;
}
運(yùn)行結(jié)果:
world被覆蓋了,就打印不出了
改:我們可以釋放后置空,就不會出現(xiàn)這種情況了文章來源:http://www.zghlxwxcb.cn/news/detail-762913.html
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
str=NULL;
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
以上就是我的分享了,如果有什么錯誤,歡迎在評論區(qū)留言。
最后,謝謝大家的觀看!文章來源地址http://www.zghlxwxcb.cn/news/detail-762913.html
到了這里,關(guān)于c語言-動態(tài)內(nèi)存管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!