C語(yǔ)言中的內(nèi)存管理詳情
內(nèi)容提要:
大家寫C程序時(shí),手工申請(qǐng)過內(nèi)存嗎?每次需要存儲(chǔ)空間時(shí)都向操作系統(tǒng)申請(qǐng)嗎?使用完申請(qǐng)到的內(nèi)存后有把它還給操作系統(tǒng)嗎?遇到過“段錯(cuò)誤”嗎?本文的主題和這一串問題有很大的關(guān)系。
1.malloc
手工申請(qǐng)內(nèi)存使用malloc。先看一段例程。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *say_hi();
int main(int argc, char *argv[])
{
char *str = say_hi();
printf("str = %s\n", str);
free(str);
return 0;
}
char *say_hi()
{
char *ptr = (char *)malloc(100);
char *str = "how are you?";
strcpy(ptr, str);
return ptr;
}執(zhí)行結(jié)果如下:
[root@localhost compiler-basic]# gcc -o t t.c -g -m32
[root@localhost compiler-basic]# ./t
str = how are you?
把修改成:
char *say_hi()
{
char *ptr = (char *)malloc(100);
char *str = "how are you?";
strcpy(ptr, str);
return str;
}執(zhí)行結(jié)果如下:
[root@localhost compiler-basic]# gcc -o t t.c -g -m32
[root@localhost compiler-basic]# ./t
str = how are you?
Segmentation fault (core dumped)
在第二版say_hi中,返回值是str,我們看到,打印出的數(shù)據(jù)是how are you?。然而,這只是偶爾正確。C語(yǔ)言的函數(shù)并不能總是正確返回非數(shù)字類型的局部變量的值。在程序變得復(fù)雜后,保不準(zhǔn)某個(gè)時(shí)候這樣的函數(shù)就不能返回預(yù)期數(shù)據(jù)。
Segmentation fault (core dumped),這又是怎么回事呢?str不是malloc申請(qǐng)到的內(nèi)存空間,用free釋放它導(dǎo)致錯(cuò)誤。
2.內(nèi)存泄露
用malloc申請(qǐng)了內(nèi)存空間卻不用free釋放,會(huì)造成內(nèi)存泄露。
在前面的第二版say_hi中,ptr指向的內(nèi)存空間就被泄露了。在程序員看來,執(zhí)行完say_hi后,ptr指向的內(nèi)存就沒有價(jià)值了;由于沒有正確地釋放它,操作系統(tǒng)認(rèn)為它仍然在使用中,當(dāng)其他進(jìn)程申請(qǐng)內(nèi)存時(shí),不會(huì)把這片內(nèi)存回收重新分配。
像這樣的內(nèi)存泄露越來越多,會(huì)一直多到耗盡所有的內(nèi)存。但實(shí)際上,那些被泄露的內(nèi)存是完全應(yīng)該被回收再使用的。
內(nèi)存泄露后,會(huì)成為操作系統(tǒng)和程序員都無(wú)法掌控的內(nèi)存。
我們?cè)谑止ど暾?qǐng)內(nèi)存、使用完畢之后,一定要釋放內(nèi)存。malloc和free猶如一對(duì)連體嬰兒,總是一起使用。
3.內(nèi)存池
前面的例子,在say_hi使用malloc,在main使用free。連體嬰兒卻只能出現(xiàn)在兩個(gè)函數(shù)中,這很危險(xiǎn)。一不留神,就會(huì)忘記釋放內(nèi)存。
每次申請(qǐng)內(nèi)存都使用malloc,需要陷入內(nèi)核,性能開銷很大。
有朋友會(huì)說,我只是個(gè)小菜鳥,暫時(shí)還不需要考慮性能開銷,只要我寫的程序能跑就行。
哈哈,你并不是第一個(gè)這么想的人,我也是這樣想的,所以我不厭其煩地、勤快地多次使用malloc。終于,在昨天,我遇到了非常煩人的段錯(cuò)誤。
段錯(cuò)誤發(fā)生在malloc中,導(dǎo)致錯(cuò)誤的函數(shù)調(diào)用鏈不同,在測(cè)試數(shù)據(jù)中隨便加幾個(gè)字符后錯(cuò)誤又消失。斷點(diǎn)調(diào)試不管用,在前幾十次執(zhí)行malloc沒有段錯(cuò)誤,在后面幾十次中的某一次執(zhí)行malloc時(shí)才出現(xiàn)段錯(cuò)誤。段錯(cuò)誤出現(xiàn)在內(nèi)核中。內(nèi)核是不會(huì)有問題的,即使問題指向內(nèi)核,那一定是向內(nèi)核提供了錯(cuò)誤的輸入數(shù)據(jù)。
在束手無(wú)策快要絕望之前,我把原始的malloc換成了向內(nèi)存池申請(qǐng)內(nèi)存。先前發(fā)生的奇怪錯(cuò)誤,再也沒有出現(xiàn)了。
4.理論
內(nèi)存池,是使用malloc申請(qǐng)的一段內(nèi)存;進(jìn)程需要內(nèi)存空間時(shí),從這段內(nèi)存中拿一塊去用;當(dāng)這段內(nèi)存被用完后,再使用malloc申請(qǐng)一段新內(nèi)存;像這樣重復(fù)這個(gè)過程。
很容易發(fā)現(xiàn),內(nèi)存池減少了使用malloc的次數(shù);在進(jìn)程結(jié)束前,程序員能方便地一次性釋放這些內(nèi)存。
5.代碼數(shù)據(jù)結(jié)構(gòu)
struct mblock{
char *begin;
char *avail;
char *end;
};
typedef struct heap{
struct mblock *last;
struct mblock head;
} *Heap;
#define HEAP(hp) struct heap hp = { &hp.head }
Heap CurrentHeap;
struct heap ProgrameHeap;
int HeapAlloc(Heap hp, int size);
void *do_malloc(int size);6.代碼
char *HeapAlloc(Heap hp, int size){
struct mblock *blk = NULL;
blk = hp->last;
while(size > blk->end - blk->avail){
int m = 4096 + sizeof(struct mblock) + size;
blk->next = malloc(m);
blk = blk->next;
if(blk == NULL){
printf("內(nèi)存耗盡\n");
exit(-1);
}
blk->begin = blk->avail = (char *)(blk + 1);
blk->end = (char *)blk + m;
hp->last = blk;
}
blk->avail += size;
return blk->avail - size;
}
void *do_malloc(int size){
CurrentHeap = HEAP(ProgrameHeap);
void *p = HeapAlloc(CurrentHeap, size);
memset(p, 0, size);
return 0;
}要申請(qǐng)內(nèi)存的時(shí)候,原來是使用malloc,現(xiàn)在,我們有了上面的這套內(nèi)存管理機(jī)制后,就使用do_malloc來申請(qǐng)內(nèi)存。
**解說
HEAP**
這個(gè)宏把heap的第一個(gè)成員last的值設(shè)置成第二個(gè)成員head的內(nèi)存地址。
要熟悉這種{ &hp.head }初始化結(jié)構(gòu)體的語(yǔ)法。
7.blk->begin
blk->begin = blk->avail = (char *)(blk + 1);
先看blk + 1。它表示,在blk的基礎(chǔ)上,往后移動(dòng)sizeof(struct mblock)個(gè)字節(jié)。指針的加減就是這么計(jì)算的。
blk指向一段m個(gè)字節(jié)的內(nèi)存空間,這段內(nèi)存空間的前sizeof(struct mblock)個(gè)字節(jié)存儲(chǔ)一個(gè)mblock結(jié)構(gòu)。怎么理解這個(gè)結(jié)構(gòu)?它是這段內(nèi)存空間的元數(shù)據(jù)。了解文件系統(tǒng)的實(shí)現(xiàn)機(jī)制的朋友會(huì)很容易理解這一點(diǎn)。
從元數(shù)據(jù)中,獲取begin、avail、end。
如果把blk->begin做如下修改。
blk->begin = blk->avail = (char *)(blk);
怎么樣?我們來推演一番。
- 第一次執(zhí)行HeapAlloc,申請(qǐng)了一段內(nèi)存,這段內(nèi)存的前面是元數(shù)據(jù);返回給進(jìn)程的是這段內(nèi)存的開始地址,也就是元數(shù)據(jù)的開始地址。
- 執(zhí)行memset(p, 0, size);,元數(shù)據(jù)被擦除。
- 再次執(zhí)行HeapAlloc,元數(shù)據(jù)end、avail不再是前一次執(zhí)行HeapAlloc后設(shè)置的值,無(wú)法知曉內(nèi)存池是否還有內(nèi)存可以分配;已經(jīng)亂套了。
blk->end最后一個(gè)問題,理解下面的代碼。
blk->end = (char *)blk + m;
blk->end表示新申請(qǐng)的這片內(nèi)存的末尾地址。
末尾地址等于初始地址加上這片內(nèi)存的長(zhǎng)度??纯瓷厦娴拇a是不是這個(gè)意思。
(char
)blk + m就是這個(gè)意思。而(char
)(blk + m)就不是這個(gè)意思。
blk是這片內(nèi)存的第一個(gè)字節(jié),(char *)blk + m-1是這片內(nèi)存的最后一個(gè)字節(jié)。
8.總結(jié)
每個(gè)內(nèi)存池的開頭都有一個(gè)mblock,存儲(chǔ)這個(gè)內(nèi)存池的元數(shù)據(jù)(begin、avail、end、next)。進(jìn)程需要內(nèi)存時(shí),先向內(nèi)存池申請(qǐng)。當(dāng)前內(nèi)存池容量不夠時(shí),再向系統(tǒng)申請(qǐng)一個(gè)內(nèi)存池。把這個(gè)內(nèi)存池連接到前一個(gè)內(nèi)存池的元數(shù)據(jù)的next上。
一個(gè)內(nèi)存池耗盡后,并非全部空間都被使用了。沒有被利用的空間,在當(dāng)前機(jī)制下,被浪費(fèi)了。以后再找機(jī)會(huì)優(yōu)化。
所有的內(nèi)存池構(gòu)成一個(gè)單鏈表。當(dāng)進(jìn)程完成它的功能后,在結(jié)束前,遍歷這個(gè)單鏈表,從元數(shù)據(jù)中獲取begin,然后調(diào)用free(begin)就能釋放所有的內(nèi)存。
到此這篇關(guān)于C語(yǔ)言中的內(nèi)存管理詳情的文章就介紹到這了,更多相關(guān)C內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言開發(fā)實(shí)現(xiàn)井字棋及電腦落子優(yōu)化示例詳解
以前上課經(jīng)常和同桌玩起井字棋,那么我們就當(dāng)我們回憶童年,現(xiàn)在也用C語(yǔ)言來實(shí)現(xiàn)井字棋,本次代碼相對(duì)于初階的井字棋,在電腦下棋代碼部分做了優(yōu)化,使得電腦更加具有威脅2021-11-11
C語(yǔ)言實(shí)現(xiàn)打印數(shù)字金字塔
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)打印數(shù)字金字塔方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
淺析C語(yǔ)言中的setjmp與longjmp函數(shù)
以下是對(duì)C語(yǔ)言中的setjmp與longjmp函數(shù)進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下2013-09-09
基于Sizeof與Strlen的區(qū)別以及聯(lián)系的使用詳解
本篇文章是對(duì)Sizeof與Strlen的區(qū)別以及聯(lián)系的使用進(jìn)行了詳細(xì)的介紹。需要的朋友參考下2013-05-05
C語(yǔ)言計(jì)算連續(xù)無(wú)序數(shù)組中缺省數(shù)字方法詳解
這篇文章主要介紹了C語(yǔ)言計(jì)算連續(xù)無(wú)序數(shù)組中缺省數(shù)字方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02
Qt QStandardItemModel用法小結(jié)
QStandardItemModel可用作標(biāo)準(zhǔn)Qt數(shù)據(jù)類型的存儲(chǔ)庫(kù),本文主要介紹了Qt QStandardItemModel用法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12
C++面試八股文之STL標(biāo)準(zhǔn)模板庫(kù)使用詳解
這篇文章主要為大家介紹了C++面試八股文之STL標(biāo)準(zhǔn)模板庫(kù)使用詳解,<BR>有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
一篇文章帶你了解C語(yǔ)言中volatile關(guān)鍵字
這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中volatile關(guān)鍵字,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-09-09

