大家好,我是蘇貝,本篇博客帶大家了解動(dòng)態(tài)內(nèi)存管理,如果你覺得我寫的還不錯(cuò)的話,可以給我一個(gè)贊??嗎,感謝??
一. 為什么存在動(dòng)態(tài)內(nèi)存分配
我們已經(jīng)掌握的內(nèi)存開辟方式有:
int val = 20; 在??臻g上開辟四個(gè)字節(jié)
char arr[10] = {0}; 在??臻g上開辟10個(gè)字節(jié)的連續(xù)空間
但是上述的開辟空間的方式有兩個(gè)特點(diǎn):
- 空間開辟大小是固定的。
- 數(shù)組在申明的時(shí)候,必須指定數(shù)組的長(zhǎng)度,它所需要的內(nèi)存在編譯時(shí)分配。
但是對(duì)于空間的需求,不僅僅是上述的情況。有時(shí)候我們需要的空間大小在程序運(yùn)行的時(shí)候才能知道,那數(shù)組的編譯時(shí)開辟空間的方式就不能滿足了。這時(shí)候就只能試試動(dòng)態(tài)存開辟了
二. 動(dòng)態(tài)內(nèi)存函數(shù)的介紹
2.1 malloc和free
C語言提供了一個(gè)動(dòng)態(tài)內(nèi)存開辟的函數(shù):
這個(gè)函數(shù)向內(nèi)存申請(qǐng)一塊連續(xù)可用的空間,并返回指向這塊空間的指針。
○如果開辟成功,則返回一個(gè)指向開辟好空間的指針
○如果開辟失敗,則返回一個(gè)NULL指針,因此malloc的返回值一定要做檢查。
○返回值的類型是 void* ,所以malloc函數(shù)并不知道開辟空間的類型,具體在使用的時(shí)候使用者自己來決定。
○如果參數(shù) size 為0,malloc的行為是標(biāo)準(zhǔn)是未定義的,取決于編譯器
○使用malloc函數(shù)后,一定要記得判斷malloc函數(shù)的返回值是否為NULL
○要記得對(duì)malloc函數(shù)開辟的空間進(jìn)行釋放
C語言提供了另外一個(gè)函數(shù)free,專門是用來做動(dòng)態(tài)內(nèi)存的釋放和回收的,函數(shù)原型如下:
free函數(shù)用來釋放動(dòng)態(tài)開辟的內(nèi)存。
○如果參數(shù) ptr 指向的空間不是動(dòng)態(tài)開辟的,那free函數(shù)的行為是未定義的。
○如果參數(shù) ptr 是NULL指針,則函數(shù)什么事都不做
malloc和free都聲明在 stdlib.h 頭文件中
示例1:
int main()
{
//申請(qǐng)一塊空間,用來存放10個(gè)整型
int* p = (int*)malloc(10 * sizeof(int));
//int* p = (int*)malloc(40);也可
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
*(p + i) = i;
for (i = 0; i < 10; i++)
printf("%d ", *(p+i));
//釋放空間
free(p);
p = NULL;
return 0;
}
示例2:
有些人可能會(huì)有疑問,malloc函數(shù)的返回值真的可能為NULL嗎?一寫代碼便知
#include<limits.h>//INT_MAX的頭文件
int main()
{
int* p = (int*)malloc(4 * INT_MAX);
if (p == NULL)
{
perror("malloc");
return 1;
}
//...
free(p);
p = NULL;
return 0;
}
malloc函數(shù)釋放的空間是怎么釋放的呢?
1.free函數(shù)釋放-----主動(dòng)釋放(最好選擇free函數(shù)釋放)
2.程序退出后,malloc函數(shù)申請(qǐng)的空間會(huì)被操作系統(tǒng)回收-----被動(dòng)
正常情況下,誰申請(qǐng)的空間誰釋放。萬一自己沒有釋放,也要叫別人釋放
釋放空間后,記得將原先指向這塊空間的起始位置的指針變?yōu)镹ULL指針,否則釋放空間結(jié)束后,該指針就成為野指針
2.2 calloc
C語言還提供了一個(gè)函數(shù)叫 calloc , calloc 函數(shù)也用來動(dòng)態(tài)內(nèi)存分配。原型如下:
○函數(shù)的功能是為 num 個(gè)大小為 size 的元素開辟一塊空間,并且把空間的每個(gè)字節(jié)初始化為0。
○與函數(shù) malloc 的區(qū)別只在于 calloc 會(huì)在返回地址之前把申請(qǐng)的空間的每個(gè)字節(jié)全部初始化為0
所以我們?nèi)绻雱?dòng)態(tài)開辟一塊空間,可以使用malloc或calloc函數(shù)。如果我們對(duì)申請(qǐng)的內(nèi)存空間的內(nèi)容要求初始化,那么可以很方便的使用calloc函數(shù)來完成任務(wù)
示例:
下面我們來證明一下calloc函數(shù)會(huì)將每個(gè)字節(jié)初始化為0
2.3 realloc
realloc函數(shù)的出現(xiàn)讓動(dòng)態(tài)內(nèi)存管理更加靈活。有時(shí)會(huì)我們發(fā)現(xiàn)過去申請(qǐng)的空間太小了,有時(shí)候我們又會(huì)覺得申請(qǐng)的空間過大了,那為了合理的使用內(nèi)存,我們一定會(huì)對(duì)內(nèi)存的大小做靈活的調(diào)整。那 realloc 函數(shù)就可以做到對(duì)動(dòng)態(tài)開辟內(nèi)存大小的調(diào)整。函數(shù)原型如下
ptr 是要調(diào)整的內(nèi)存地址,size 調(diào)整之后的新大小,返回值為調(diào)整之后的內(nèi)存起始位置。這個(gè)函數(shù)調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,可能還會(huì)將原來內(nèi)存中的數(shù)據(jù)移動(dòng)到新的空間。
即:realloc在調(diào)整內(nèi)存空間的是存在兩種情況:
○1:原有空間之后有足夠大的空間
○2:原有空間之后沒有足夠大的空間
情況1:后面有足夠空間時(shí),要擴(kuò)展內(nèi)存就直接原有內(nèi)存之后直接追加空間,原來空間的數(shù)據(jù)不發(fā)生變化。
情況2:后面沒有足夠多的空間時(shí),擴(kuò)展的方法是:在堆空間上另找一個(gè)合適大小的連續(xù)空間來使用。此時(shí)會(huì)將舊的空間中的數(shù)據(jù)拷貝到新的空間中,再將舊的空間釋放掉,再返回新的空間的起始地址
示例:
一開始我們動(dòng)態(tài)內(nèi)存開辟了16個(gè)字節(jié)的空間,后面發(fā)現(xiàn)不夠,需要開辟40個(gè)字節(jié)的空間
注意:
realloc函數(shù)也可能會(huì)開辟空間失敗,因此它的返回值也可能是NULL,所以不要用指針p直接接收函數(shù)返回值,否則p可能變成NULL指針
int main()
{
int* p = (int*)calloc(4, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
//...
int* tmp = (int*)realloc(p, 40);
if (tmp != NULL)
{
p = tmp;
}
free(p);
p = NULL;
return 0;
}
三. 常見的動(dòng)態(tài)內(nèi)存錯(cuò)誤
1.對(duì)NULL指針的解引用操作
如果p的值是NULL,就是對(duì)NULL指針解引用,這是非法的。所以一定要記得在使用malloc函數(shù)后,判斷返回值是否為NULL指針
int main()
{
int* p = (int*)malloc(40);
*p = 20;
free(p);
p = NULL;
return 0;
}
2.對(duì)動(dòng)態(tài)開辟空間的越界訪問
int main()
{
int* p = (int*)malloc(4 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i <= 10; i++)
*(p + i) = i;//當(dāng)i是10的時(shí)候越界訪問
return 0;
}
3.對(duì)非動(dòng)態(tài)開辟內(nèi)存使用free釋放
非動(dòng)態(tài)開辟的內(nèi)存空間不能用free函數(shù)釋放
int main()
{
int a = 10;
int* p = &a;
free(p);
return 0;
}
4.使用free釋放一塊動(dòng)態(tài)開辟內(nèi)存的一部分
到最后free函數(shù)釋放空間時(shí),指針p指向的是第6個(gè)元素,所以只釋放從第6個(gè)元素開始到空間末尾的空間,而第6個(gè)元素之前的元素所在的空間沒有被free函數(shù)釋放,這樣并不好。所以我們最好一直讓p指向開辟空間的起始位置
int main()
{
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;
return 0;
}
5.對(duì)同一塊動(dòng)態(tài)內(nèi)存多次釋放
int main()
{
int* p = (int*)malloc(40);
free(p);
free(p);//重復(fù)釋放
return 0;
}
6.動(dòng)態(tài)開辟內(nèi)存忘記釋放(內(nèi)存泄漏)
在函數(shù)調(diào)用結(jié)束前未釋放動(dòng)態(tài)開辟的內(nèi)存
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
四. 幾個(gè)經(jīng)典的筆試題
請(qǐng)問運(yùn)行下面的Test 函數(shù)會(huì)有什么樣的結(jié)果?
4.1
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);//ok
}
int main()
{
Test();
return 0;
}
程序崩潰了。test函數(shù)中,將str指針作為實(shí)參調(diào)用GetMenory函數(shù),指針變量p是str的一份臨時(shí)拷貝,它也是NULL。動(dòng)態(tài)內(nèi)存開辟空間,若開辟成功,p中存放的是開辟的空間的起始地址。出GetMenory,指針變量p的空間被釋放。這次調(diào)用函數(shù)沒有改變指針變量str,str仍是NULL,后面非法對(duì)NULL進(jìn)行解引用操作,程序崩潰。這段代碼還有一個(gè)問題,在退出程序前未釋放開辟的空間,造成內(nèi)存泄漏。
有人可能會(huì)對(duì)printf(str);這條語句的正確性產(chǎn)生質(zhì)疑,其實(shí)該語句是正確的,只是我們平時(shí)確實(shí)不太用。解釋:printf(“haha”);這條語句我們都知道是正確的,實(shí)際上這是將常量字符串haha的首元素h的地址傳給printf函數(shù),打印從該地址開始往后直到遇見\0停止的字符。那printf(str);就是打印從str指向位置開始往后直到遇見\0停止的字符。所以該語句是正確的
現(xiàn)在對(duì)上面代碼修改使之成為正確的代碼
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
這段代碼調(diào)用GetMemory函數(shù)時(shí),將值傳遞變?yōu)榈刂穫鬟f,*p就是str指針,動(dòng)態(tài)開辟了100個(gè)字節(jié)的空間后,將空間起始地址傳給str指針,再調(diào)用strcpy函數(shù),最后釋放空間
4.2
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
打印的是亂碼。原因:GetMemory函數(shù)無參數(shù),在函數(shù)里面定義的p數(shù)組在出函數(shù)時(shí)所占的空間被釋放,即使返回了p數(shù)組的首元素地址并賦值給str,因?yàn)閿?shù)組空間被釋放,所以str是野指針
4.3
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
Test();
return 0;
}
根據(jù)我們對(duì)上面2段代碼的解釋,這段代碼只是沒有主動(dòng)釋放malloc開辟的空間,內(nèi)存泄漏,所以如果要修改的話,只要free(str);str=NULL;
4.4
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
上面代碼中,用malloc開辟了100個(gè)字節(jié)的空間,如果開辟成功的話返回的是開辟的空間的起始地址,再調(diào)用strcpy函數(shù),然后釋放掉開辟的空間。雖然開辟的空間被釋放了,但是指針變量str仍然指向開辟空間的起始地址,此時(shí)str就是野指針,因?yàn)椴皇荖ULL,所以會(huì)調(diào)用strcpy函數(shù),會(huì)對(duì)野指針進(jìn)行操作,非法訪問內(nèi)存
五. C/C++程序的內(nèi)存開辟
C/C++程序內(nèi)存分配的幾個(gè)區(qū)域:
- 棧區(qū)(stack):在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。 棧區(qū)主要存放運(yùn)行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回?cái)?shù)據(jù)、返回地址等。
- 堆區(qū)(heap):一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS(操作系統(tǒng))回收 。分配方式類似于鏈表。
- 數(shù)據(jù)段(靜態(tài)區(qū)):(static)存放全局變量、靜態(tài)數(shù)據(jù)。程序結(jié)束后由系統(tǒng)釋放。
- 代碼段:存放函數(shù)體(類成員函數(shù)和全局函數(shù))的二進(jìn)制代碼
有了這幅圖,我們就可以更好的理解之前講的static關(guān)鍵字修飾局部變量的例子了。
實(shí)際上普通的局部變量是在棧區(qū)分配空間的,棧區(qū)的特點(diǎn)是在上面創(chuàng)建的變量出了作用域就銷毀。但是被static修飾的變量存放在數(shù)據(jù)段(靜態(tài)區(qū)),數(shù)據(jù)段的特點(diǎn)是在上面創(chuàng)建的變量,直到程序結(jié)束才銷毀,所以生命周期變長(zhǎng)。
六. 柔性數(shù)組
也許你從來沒有聽說過柔性數(shù)組(flexible array)這個(gè)概念,但是它確實(shí)是存在的。
C99 中,結(jié)構(gòu)中的最后一個(gè)元素允許是未知大小的數(shù)組,這就叫做『柔性數(shù)組』成員
例如:
typedef struct sa
{
int i;
int a[0];//柔性數(shù)組成員
}sa;
有些編譯器會(huì)報(bào)錯(cuò)無法編譯可以改成:
typedef struct sa
{
int i;
int a[];//柔性數(shù)組成員
}sa;
6.1 柔性數(shù)組的特點(diǎn)
1.結(jié)構(gòu)中的柔性數(shù)組成員前面必須至少一個(gè)其他成員。
2.sizeof 返回的這種結(jié)構(gòu)體大小不包括柔性數(shù)組的內(nèi)存。
3.包含柔性數(shù)組成員的結(jié)構(gòu)用malloc ()函數(shù)進(jìn)行內(nèi)存的動(dòng)態(tài)分配,并且分配的內(nèi)存應(yīng)該大于結(jié)構(gòu)的大小,以適應(yīng)柔性數(shù)組的預(yù)期大小。
示例1:
typedef struct sa
{
char c;//1
int i;//4
int a[0];//柔性數(shù)組成員
}sa;
int main()
{
printf("%d\n", sizeof(sa));
return 0;
}
根據(jù)結(jié)構(gòu)體內(nèi)存對(duì)齊,結(jié)構(gòu)體的前兩個(gè)元素就占了8個(gè)字節(jié),結(jié)構(gòu)體大小不包括柔性數(shù)組的內(nèi)存。所以這也說明了第一個(gè)特點(diǎn):結(jié)構(gòu)中的柔性數(shù)組成員前面必須至少一個(gè)其他成員。
示例2:
typedef struct sa
{
char c;
int i;
int a[0];//柔性數(shù)組成員
}sa;
int main()
{
sa* pc = (sa*)malloc(sizeof(sa) + 20);
if (pc == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
pc->a[i] = i;
for (i = 0; i < 5; i++)
printf("%d ", pc->a[i]);
free(pc);
pc=NULL;
return 0;
}
6.3 柔性數(shù)組的優(yōu)勢(shì)
上面示例2的代碼如果不想寫成柔性數(shù)組的形式,又要可變的話,那也可以寫成下面這種形式。但這種形式需要2次malloc,2次free,所以不如柔性數(shù)組方便
typedef struct sa
{
char c;
int i;
int *a;
}sa;
int main()
{
sa* pc = (sa*)malloc(sizeof(sa) + 20);
if (pc == NULL)
{
perror("malloc1");
return 1;
}
pc->a = (int*)malloc(20);
if (pc->a == NULL)
{
perror("malloc2");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
pc->a[i] = i;
for (i = 0; i < 5; i++)
printf("%d ", pc->a[i]);
//釋放
free(pc->a);
pc->a = NULL;
free(pc);
pc = NULL;
return 0;
}
柔性數(shù)組的好處:
1:方便內(nèi)存釋放
如果我們的代碼是在一個(gè)給別人用的函數(shù)中,你在里面做了二次內(nèi)存分配,并把整個(gè)結(jié)構(gòu)體返回給用戶。用戶調(diào)用free可以釋放結(jié)構(gòu)體,但是用戶并不知道這個(gè)結(jié)構(gòu)體內(nèi)的成員也需要free,所以你不能指望用戶來發(fā)現(xiàn)這個(gè)事。所以,如果我們把結(jié)構(gòu)體的內(nèi)存以及其成員要的內(nèi)存一次性分配好了,并返回給用戶一個(gè)結(jié)構(gòu)體指針,用戶做一次free就可以把所有的內(nèi)存也給釋放掉。
2:這樣有利于訪問速度,連續(xù)的內(nèi)存有益于提高訪問速度,也有益于減少內(nèi)存碎片文章來源:http://www.zghlxwxcb.cn/news/detail-794713.html
好了,那么本篇博客就到此結(jié)束了,如果你覺得本篇博客對(duì)你有些幫助,可以給個(gè)大大的贊??嗎,感謝看到這里,我們下篇博客見??文章來源地址http://www.zghlxwxcb.cn/news/detail-794713.html
到了這里,關(guān)于【C語言】動(dòng)態(tài)內(nèi)存管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!