前言
在動態內存函數的使用過程中我們可能會遇到一些錯誤,這里將常見的錯誤進行總結。
對NULL解引用
請看以下代碼:
可以看到,這時我們的malloc開辟是失敗的,所以返回的是空指針NULL,而我們卻沒有進行檢查,而是直接對其解引用,最終想要打印這塊內存的內容,也是失敗的。
這里的下波浪線是vs給出的沒有進行檢查的警告,即使malloc申請的空間沒有那么大可以成功申請,最終也能正常打印,這個警告依然會存在。
總之,就是不要忘記檢查返回值是否為NULL。
對動態開辟空間的越界訪問
動態開辟的空間也有自己的大小,所以也有越界。
可以看到,此時vs很智能地給出了警告。?
對非動態開辟內存使用free釋放
int main()
{int a = 2077;int* p = &a;free(p);p = NULL;return 0;
}
這段代碼中,p指向的明明不是動態開辟來的空間,卻對它進行free,會發生什么呢?
可以看到會出現這樣的后果。(動態內存相關的錯誤常見現象)
?
使用free釋放動態開辟內存的一部分
我們知道,如果用*(p+i),在訪問數組的過程中,i在改變,但p始終沒有改變。
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{perror("malloc");return 1;
}int i = 0;
for(i=0;i<5;i++)
{*(p+i)=i;
}free(p);
p=NULL;
而我們還有另一種移動指針的寫法:
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{perror("malloc");return 1;
}int i = 0;
for(i=0;i<5;i++)
{*p=i;p++;
}free(p);
p=NULL;
在這種寫法中,p自身發生了改變。當退出循環時,由于p++,此時我們的p指向的是第6個元素(數組只有5個元素)。所以我們的p現在不指向動態開辟內存的起始位置。而使用free必須是傳起始地址。
所以,我們不應該讓p亂動。
對同一塊動態內存多次釋放
如果是這樣的多次釋放:
free(p);
p = NULL;free(p);
p = NULL;
這倒是沒什么問題,因為前面已經把p置為NULL了,而對NULL進行釋放會什么都不做。
但如果是這樣的多次釋放,就會有問題:
free(p);free(p);
p = NULL;
因為我們并沒有把p置為NULL,那么此時p成為了野指針,我們對野指針進行釋放,這是有問題的。
所以又可以再次看出,將p釋放后及時置為NULL的重要性。
動態開辟內存忘記釋放(內存泄漏)
這是各類問題中相當令人頭疼的一個。開辟了內存,也使用了,但是忘記釋放了。
比如,我們在調用free()之前寫了會提前返回的代碼(這是很有可能發生的):
void test()
{int flag = 1;int* p = (int*)malloc(100);if (p == NULL){return;}//假設這里使用了這塊內存if (flag)//假設某個條件發生了,就提前返回return;free(p);p=NULL;
}int main()
{test();//假設這里還有很多代碼return 0;
}
所以這時,我們開辟來的這塊空間沒有機會釋放了。
而且從test回到主函數后,還有很多代碼,已經找不回這塊空間了,也就是內存泄漏了。
只有等到主函數徹底結束,這塊空間才會回收。
內存泄漏指的是一塊空間動態開辟后,使用完又不釋放,也可能再沒法釋放,這塊空間就相當于消失或者說泄漏了。
所以可以看到,即使我們的malloc和free已經成對使用了,也有可能出現無法釋放的情況,這樣的內存泄漏的問題必須在寫代碼時小心,在出問題時也得慢慢去查。
(如果想讓后面的程序去釋放,那就要把指針返回給后面的程序)
?內存泄漏有多可怕呢,比如一些服務器程序,24/7,一直在運行,如果內存泄漏,一會吃掉一點內存,可能就把內存耗干了,機器就掛了。
總結
動態內存是一把雙刃劍,能夠提供靈活的內存管理方式,同時也會帶來風險。
到此,本文就結束了,祝閱讀愉快^_^