C語言編程動態(tài)內(nèi)存分配常見錯誤全面分析
前言:為什么存在動態(tài)內(nèi)存分配?
我們已經(jīng)掌握的內(nèi)存開辟方式如下
int a=10;//在??臻g上開辟4字節(jié)
char arr[10]={0};//在棧空間上開辟10字節(jié)連續(xù)空間
以上開辟空間的方法有兩個缺點:
1.空間開辟的大小是固定的。
2.數(shù)組在聲明的時候,必須指定數(shù)組長度,它所需要的內(nèi)存在編譯時進行分配。
但是對于空間的需求,不僅僅是上述的情況。有時我們需要的空間大小在程序運行時才能知道,這時上述的方法就不能滿足需要了,我們來介紹一種解決方案:動態(tài)內(nèi)存分配
一、動態(tài)內(nèi)存函數(shù)

(圖片來自比特就業(yè)課)
計算機在使用時會有三個區(qū):常見的有棧區(qū)——用來存放局部變量、函數(shù)形式參數(shù);靜態(tài)區(qū)——用來存放靜態(tài)區(qū)和全局變量;最后一個堆區(qū)則是我們用來動態(tài)內(nèi)存分配的,學習動態(tài)內(nèi)存分配必須掌握以下4種函數(shù):
1.malloc和free函數(shù)
malloc函數(shù)聲明:
void*malloc(size_t size);//size_t即unsigned int
該函數(shù)向內(nèi)存申請一塊連續(xù)可用的空間,并返回指向這塊空間的指針。注意點如下:
1.如果開辟成功,返回一個指向開辟空間的指針
2.如果開辟失敗,返回一個NULL指針
(由于是NULL指針,所以對于malloc函數(shù)常用assert進行檢查)
3.返回值類型為void*,所以malloc函數(shù)開辟空間的類型需要使用者自己進行決定
4.如果size=0,malloc的行為取決具體編譯器
free函數(shù)聲明
void free(void*ptr);
free函數(shù)用來釋放動態(tài)開辟的空間,注意點如下:
1.如果參數(shù)ptr指向的空間不是動態(tài)開辟的,那free函數(shù)的行為是未知的
2.如果ptr是空指針,free函數(shù)什么也不做
實戰(zhàn)舉例:
#include<stdio.h>
#include<stdlib.h>//malloc和free函數(shù)頭文件
int main()
{
int*p=(int*)malloc(40);//malloc出來的空間是不確定類型的,需要你自己強轉(zhuǎn)一個類型
//這里我們把它轉(zhuǎn)換成int*,由整型指針對這塊空間進行維護
//那這里會是4個字節(jié)看成一個元素,40個字節(jié),可填充10個int元素
if (p == NULL)
{
return -1;
}//如果沒有返回值則說明開辟成功
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i)=i;//對開辟空間進行賦值操作
}
free(p);//用完之后不需要了,用free函數(shù)釋放p所指向的空間(40個字節(jié)全部釋放掉,還給操作系統(tǒng))
p = NULL;//由于p存儲的是開辟空間的地址,即使空間還給了操作形態(tài),但p還是可以找到這塊空間,這是非常危險的,所以我們用一個空指針賦給p
return 0;
}
2.calloc函數(shù)
calloc函數(shù)也是用來動態(tài)內(nèi)存分配的,函數(shù)聲明如下
void*calloc(size_t num,size_t size)
該函數(shù)功能是為num個大小為size的元素開辟一塊空間,并且把空間的每個字節(jié)都初始化為0
它與malloc函數(shù)的區(qū)別只是在返回地址前會把申請空間里的每個字節(jié)都初始化為0
代碼如下(示例):
#include<stdio.h>
#include<stdlib.h>//calloc函數(shù)頭文件
#include<string.h>//strerror函數(shù)頭文件
#include<errno.h>//errno頭文件
int main()
{
int*p = (int*)calloc(10, sizeof(int));//開辟10個大小為int型的空間
if (p == NULL)//calloc函數(shù)也有可能開辟空間失敗,需要進行檢驗
{
printf("%s\n", strerror(errno));//errno是錯誤碼,strerror會把錯誤碼轉(zhuǎn)換成相應的錯誤信息
return -1;
}
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", *(p + i));//會打印10個0(calloc會自己初始化為0)
}
free(p);
p = NULL;
return 0;
}
3.realloc函數(shù)
realloc函數(shù)是在已有空間不夠的情況下,進行追加申請空間的函數(shù),其聲明如下:
void*realloc(void*memblock,size_t size);
realloc函數(shù)是為了讓動態(tài)內(nèi)存管理更加靈活,有時候我們發(fā)現(xiàn)之前申請的空間過少了,或者過大了,我們可以用realloc函數(shù)來進行增減,它的注意點如下:
1.ptr是要調(diào)整的內(nèi)存地址
2.size是調(diào)整之后新的大小
3.返回值是調(diào)整后的內(nèi)存起始位置
4.該函數(shù)在調(diào)整原先內(nèi)存大小的基礎上,還會將原來內(nèi)存的數(shù)據(jù)復制到新空間
5.realloc函數(shù)在調(diào)整內(nèi)存空間存在兩種情況
關于4和5解釋如下:
假設我們現(xiàn)在追加1倍空間

第一種:如上圖,紅色是我們開辟的空間,藍色是其他程序正在使用的空間,又因為我們要開辟空間肯定是物理上連續(xù)的,我們又不能使用藍色部分,中間的空白空間又完全不夠原先空間的兩倍,那怎么辦?

我們會另外找一塊足夠空間的地方進行原空間兩倍的開辟,并且把原空間內(nèi)數(shù)據(jù)進行復制到新空間,函數(shù)返回新空間的地址。
第二種:這種就比較簡單了,原先空間后面就足夠追加開辟一塊1倍的空間,我們直接進行開辟即可。


函數(shù)直接會返回原先空間的首地址。
當然了,realloc函數(shù)同malloc和calloc函數(shù)一樣,也有可能開辟空間失敗,所以依然需要檢驗是否返回的是空指針
#include<stdio.h>
#include<stdlib.h>//malloc和free函數(shù)頭文件
int main()
{
int*p=(int*)malloc(40);//開辟10個int大小的空間
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i)=i;//對開辟空間進行賦值操作
}
int*ptr = realloc(p, 20 * sizeof(int));//注意!這里20*sizeof(int)是新的大小
//比如我現(xiàn)在只有10個,我需要20個,差10個。但這里不是寫10,而是寫新的大小20
if (ptr != NULL)
{
p = ptr;
}
for (i = 10;i < 20;i++)//對新開辟空間賦值
{
*(p + i) = i;
}
for (i = 0;i < 20;i++)
{
printf("%d ", *(p + i));//打印0-19
}
free(p);
p = NULL;
return 0;
}
二、常見錯誤
1.對NULL指針解引用
代碼如下(示例):
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(20);
*p = 0;//對p這個地址解引用并賦值為0
free(p);
return 0;
}
malloc等等函數(shù)在開辟空間時都是有可能開辟失敗的,萬一失敗,就是返回空指針,你直接對空指針解引用并賦值肯定是有問題的
所以我們這里還是要進行指針檢驗
代碼如下(示例):
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(20);
if(p==NULL)
{
return -1;
//如果返回,開辟失敗結(jié)束程序,如果沒有返回則可進行下面的操作
}
*p = 0;//對p這個地址解引用并賦值為0
free(p);
return 0;
}
2.對動態(tài)開辟空間的越界訪問
代碼如下(示例):
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(200);//200個字節(jié)也就是50個int型
if (p == NULL)
{
return -1;
}
int i = 0;
for (i = 0;i < 60;i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
這個代碼乍一看上去沒有問題,但是仔細看的話就會發(fā)現(xiàn)端倪,malloc開辟200字節(jié)空間也就是50個int型,你for循環(huán)賦值最多循環(huán)次數(shù)也只能是50次啊,你循環(huán)60次肯定是越界訪問了,這里也是妥妥的會報錯。
3.對非動態(tài)開辟使用free函數(shù)
代碼如下(示例):
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a = 10;
int*p = &a;
free(p);
p = NULL;
}
我們這里int創(chuàng)建了a,然后把a的地址賦給了int*類型的p,再然后free掉p。這種操作也是鐵定會報錯的,p這個局部變量是在棧上的,而free函數(shù)針對的是堆區(qū)
4.使用free釋放一塊動態(tài)內(nèi)存開辟內(nèi)存的一部分
代碼如下(示例):
//使用free釋放一塊動態(tài)內(nèi)存開辟內(nèi)存的一部分
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return -1;
}
//使用
int i = 0;
for (i = 0;i < 5;i++)
{
*p++ = i;
}
//釋放
free(p);
p = NULL;
}
這里的代碼有什么問題呢?我們畫一個圖就一目了然了

一開始p在上圖位置,然而隨著for循環(huán),p++這個操作,p指向的位置不斷往后,一直到下圖位置

這時p已經(jīng)不指向原先開辟空間的位置了,你這時候去用free釋放掉顯然是不合適的
5.對同一塊空間多次釋放
我們先來看2段代碼:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
free(p);
free(p);
}
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
free(p);
p = NULL;
free(p);
}
兩段代碼都是對同一塊空間多次釋放,但第一段代碼會報錯,第二段不會。
解釋如下:
第一段代碼你已經(jīng)釋放掉p所指向的空間了,空間里什么也沒有了,但p仍然指向那塊空間,所以你再次釋放不屬于你的空間肯定會報錯。
第二段代碼你釋放掉p所指向空間,然后用空指針給p賦值,再去釋放空指針,我們知道,free空指針是什么也不做,所以不會報錯。
6.動態(tài)開辟內(nèi)存忘記釋放
對于動態(tài)開辟內(nèi)存忘記釋放,在堆區(qū)上申請的空間有2種回收方式:
1.你自己free掉
2.程序退出時,系統(tǒng)自動回收
我們先來看一段代碼
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
getchar();
return 0;
}
該代碼我們沒有自己使用free來釋放內(nèi)存,而中間又有getchar一直在等待接收字符,打個比方:假如你中途去上廁所或者干其他事情了,getchar一直沒有接收到字符,程序就一直沒有結(jié)束,那我們用p開辟的空間在你上廁所期間就一直被占用,那塊空間系統(tǒng)沒辦法去做別的有意義的事情。而上升到將來公司層面:我們寫的程序可能一天24h都在跑,那遇到這種情況,你沒有free掉內(nèi)存,你不用又不回收,整體效率的影響是非常大的。
總結(jié)
今天介紹了動態(tài)內(nèi)存分配函數(shù)和一些常見的動態(tài)內(nèi)存分配的錯誤,希望讀者學習有所收獲,祝讀者學業(yè)有成,萬事順心!
更多關于C語言動態(tài)內(nèi)存分配的資料請關注腳本之家其它相關文章!
相關文章
美化你的代碼 vb(VBS)代碼格式化的實現(xiàn)代碼
雖然VB.NET出現(xiàn)很久了,但還有好多人仍然在使用VB6。我在實現(xiàn)一些小功能的時候也喜歡用VB6,畢竟誰都不想每天的美好心情被VS那烏龜般的啟動速度影響2012-05-05
visual studio code 編譯運行html css js文件的教程
這篇文章主要介紹了visual studio code 如何編譯運行html css js文件,本文通過圖文實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03

