malloc函數(shù)和free函數(shù)
假設(shè)您的程序在執(zhí)行過程中需要分配一定量的內(nèi)存。您可以隨時(shí)調(diào)用malloc函數(shù)從堆中申請(qǐng)一塊內(nèi)存。在操作系統(tǒng)為您的程序預(yù)留出這塊內(nèi)存,之后您就可以隨意使用它了。用完之后,要使用free函數(shù)將這塊內(nèi)存返回給操作系統(tǒng)進(jìn)行回收。以后其他程序還可以按自己的需要預(yù)留這塊內(nèi)存。
作為例子,下面的代碼演示了最簡(jiǎn)單的使用堆的方法:
?
int main() {int *p;p = (int *)malloc(sizeof(int));if (p==0) {printf("錯(cuò)誤:內(nèi)存不足n");return 1; }*p=5;printf("&dn", *p);free(p);return 0;}
程序的開始調(diào)用了malloc函數(shù),這個(gè)函數(shù)做了三件事:
- malloc語句首先檢查堆上的空閑內(nèi)存總數(shù),然后判斷:“有沒有足夠的空閑內(nèi)存可以分配一個(gè)所申請(qǐng)的大小的內(nèi)存塊呢?”申請(qǐng)的內(nèi)存塊大小是由傳入malloc的參數(shù)確定的——本例中的sizeof(int)是4個(gè)字節(jié)。若內(nèi)存不足,malloc函數(shù)會(huì)返回零地址告知發(fā)生的錯(cuò)誤(零地址的另一種表示是NULL,它在C代碼中很常用)。否則malloc函數(shù)繼續(xù)執(zhí)行。
- 若堆上有足夠的內(nèi)存,系統(tǒng)就從堆上“分配”或“預(yù)留”出指定大小的內(nèi)存塊。預(yù)留的目的是為了防止多個(gè)malloc語句恰巧使用同一個(gè)內(nèi)存塊。
- 接下來系統(tǒng)將預(yù)留出的內(nèi)存塊的地址保存到指針變量中(本例中就是p)。指針變量本身保存了一個(gè)地址。被分配的內(nèi)存塊能夠存儲(chǔ)一個(gè)指定類型的數(shù)值,而指針正是指向此數(shù)值。
下圖顯示了調(diào)用malloc之后的內(nèi)存狀態(tài):
?
右邊的方框表示malloc分配的內(nèi)存塊。
接著程序用if (p==0)檢查指針p以確定分配申請(qǐng)成功(此行也可寫成if (p==NULL)甚至if (!p))。如果分配失敗(p等于零),則程序終止,否則程序?qū)⒎峙涞膬?nèi)存塊初始化為5,然后打印內(nèi)存塊的值,接著調(diào)用free函數(shù)將內(nèi)存塊返還給堆,最后退出。
前面的章節(jié)有一段代碼是將p賦值為一個(gè)現(xiàn)成整數(shù)i的地址,而本例中的代碼和那段代碼實(shí)際上并無不同。區(qū)別只是在于:對(duì)于變量i的內(nèi)存,它是程序預(yù)分配內(nèi)存空間的一部分,有兩個(gè)名字i和*p;而對(duì)于從堆上分配的內(nèi)存,它只有一個(gè)名字*p,且是在程序運(yùn)行中分配的。兩個(gè)常見的問題是:
- 每次分配內(nèi)存后都要檢查指針的值是否為零,這真的很重要嗎?是的。因?yàn)槎训拇笮∪Q于當(dāng)前正在運(yùn)行哪些程序、它們分配了多少內(nèi)存等諸多因素,所以一直都在變化,不能保證調(diào)用malloc總是成功的。每次調(diào)用malloc以后,您都應(yīng)該檢查一下指針以確保其有效性。
- 如果程序結(jié)束前我忘記了釋放分配的內(nèi)存塊會(huì)怎樣呢?程序結(jié)束以后,操作系統(tǒng)會(huì)做“善后處理”:釋放可執(zhí)行代碼、棧、全局變量和所有從堆上分配的內(nèi)存空間以供回收利用。因此,對(duì)分配的內(nèi)存置之不理,在程序結(jié)束以后是不會(huì)對(duì)系統(tǒng)造成持續(xù)影響的。但是這種做法會(huì)被認(rèn)為是“不良的風(fēng)格”,且程序運(yùn)行中的“內(nèi)存泄漏”是有害的。這一點(diǎn)下文還會(huì)講到。
下面兩段程序顯示了兩種不同的使用指針的正確方法,旨在區(qū)分指針和指針的值在使用上的區(qū)別:
?
void main() {int*p, *q;p=(int *)malloc(sizeof(int));q=p;*p=10;printf("%dn", *q);*q=20; printf("%dn", *q);}
?
此程序的最后輸出結(jié)果是代碼第4行打印的10和代碼第6行打印的20。下面是一個(gè)內(nèi)存狀態(tài)示意圖:
?
下面這個(gè)程序稍有不同:
?
void main() {int *p, *q;p=(int *)malloc(sizeof(int));q=(int *)malloc(sizeof(int)); *p=10;*q=20;*p=*q;printf("%dn", *p);}
此程序的最后輸出結(jié)果是代碼第6行打印的20。下面是它的內(nèi)存狀態(tài)示意圖:
?
注意,編譯器會(huì)接受*p=*q,因?yàn)?p和*q都是整數(shù)。這條語句的意思是說:“將q指向的整數(shù)傳送到p指向的整數(shù)中去。”被傳送的是數(shù)值。編譯器也會(huì)接受p=q,因?yàn)閜和q都是指針且指向相同的類型(若s為指向字符的指針則p=s是不允許的,因?yàn)樗鼈冎赶虿煌愋停=q這條語句的意思是說:“將p指向和q相同的內(nèi)存位置。”換句話說,q指向的地址被傳送到了p,因此兩個(gè)指針指向相同的地址。被傳送的是地址。
從這些例子可以知道,初始化指針的方式有四種。在程序中聲明一個(gè)指針時(shí)(如int *p),它開始處于未初始化狀態(tài)。它可能指向任何位置,因此對(duì)它的解引用(取出指針指向的地址中的內(nèi)容)是錯(cuò)誤的。初始化指針就是將其指向一個(gè)已知的內(nèi)存地址。
-
第一種方式是例子中使用的malloc語句。此語句從堆上分配一塊內(nèi)存并將指針指向它。這樣指針便完成了初始化,因?yàn)樗F(xiàn)在保存了一個(gè)有效地址,即新分配的內(nèi)存塊的地址。
?
-
第二種方式,也是剛才用到的,是用p=q這樣的語句使p指向和q相同的位置。若q已經(jīng)指向有效地址,則p完成初始化。指針p將保存q已經(jīng)保存的有效地址。但若q未初始化或無效,則這個(gè)無價(jià)值的地址也會(huì)傳給 p。
?
-
第三種方式是將指針指向已知地址,如一個(gè)全局變量的地址。例如,若i是一個(gè)整數(shù)且p是一個(gè)整型指針,則語句p=&i初始化p為指向i。
?
-
第四種方式是將指針初始化為零。在使用指針時(shí)零是一個(gè)特殊值,如下所示:
?
p=0;
?
或:
?
p=NULL;
?
此語句完成的操作是將零賦給p。指針p指向的地址為零。一般用下面的示意圖表示這種情況:
?
?
任何指針都可設(shè)為指向零地址。雖然p指向零地址,但是它卻不指向任何真正的內(nèi)存塊。此指針保存的零值只是一個(gè)標(biāo)志。可以像下面語句這樣使用它:
?
if (p==0) {...}
或:
?
while (p!=0){...}
?
系統(tǒng)會(huì)識(shí)別零值,如果您無意中解引用一個(gè)零指針,系統(tǒng)會(huì)報(bào)錯(cuò)。例如下列代碼:
?
p=0;*p=5;
?
程序一般會(huì)崩潰。指針p不指向內(nèi)存塊而是零地址,所以不能為*p賦值。后面我們講到鏈表時(shí),零指針將被作為一個(gè)標(biāo)志使用。
malloc命令用于分配一個(gè)內(nèi)存塊。當(dāng)此內(nèi)存塊不再需要時(shí)還可以將其釋放。釋放的內(nèi)存塊可以被后來的malloc語句重新分配,這樣系統(tǒng)就可以回收內(nèi)存。釋放內(nèi)存的命令叫做free,它接受一個(gè)指針作為參數(shù)。free命令完成兩件事情:
- 不再預(yù)留指針指向的內(nèi)存塊,而是將其返還到堆上的空閑內(nèi)存區(qū)。此內(nèi)存塊可以被隨后的語句重新使用。
- 指針被置為未初始化的狀態(tài),再次使用前必須重新初始化。
free語句只是將指針還原為未初始化狀態(tài)并使內(nèi)存塊在堆上重新變成可用狀態(tài)。
下例顯示了如何使用堆。它分配了一塊整數(shù)內(nèi)存,寫入數(shù)據(jù)然后輸出,最后廢除此內(nèi)存塊:
?
#includeint main() {int *p;p=(int *)malloc (sizeof(int));*p=10;printf("%d n",*p);free(p);return 0;}
此代碼其實(shí)只適用于在C中演示分配、使用和釋放內(nèi)存塊的過程。malloc用于分配一塊指定大小的內(nèi)存,本例中是sizeof(int)字節(jié)(4字節(jié))。C語言的sizeof命令以字節(jié)為單位返回任何類型的大小。代碼中完全可以寫成malloc(4),因?yàn)樵诖蟛糠謾C(jī)器上sizeof(int)等于4個(gè)字節(jié)。但是使用sizeof可以大大增強(qiáng)代碼的可移植性和可讀性。
malloc函數(shù)返回一個(gè)指向被分配內(nèi)存塊的指針。這是一個(gè)通用指針,若不經(jīng)類型轉(zhuǎn)換即使用一般會(huì)導(dǎo)致編譯器發(fā)出類型警告。類型轉(zhuǎn)換(int *)將malloc返回的通用指針轉(zhuǎn)換為一個(gè)“指向整數(shù)的指針”,即與p一致。C中的free語句將內(nèi)存塊返還給堆以供重新使用。
第二個(gè)例子說明的函數(shù)和前一例相同,但是用結(jié)構(gòu)體代替了整數(shù)。C代碼如下:
?
#includestruct rec {int i;float f;char c;}; int main() {struct rec *p;p=(struct rec *) malloc (sizeof(struct rec));(*p).i=10; (*p).f=3.14; (*p).c='a'; printf("%d %f %c n",(*p).i,(*p).f,(*p).c); free(p); return 0;}
請(qǐng)注意這行:
?
(*p).i=10;
很多人不明白為什么不能寫成:
?
*p.i=10;
答案是這和C語言的操作符優(yōu)先級(jí)有關(guān)。5+3*4的結(jié)果是17,不是32,因?yàn)樵诖蠖鄶?shù)計(jì)算機(jī)語言中*比+有更高的優(yōu)先級(jí)。C語言中,操作符.比*有更高的優(yōu)先級(jí),所以要使用括號(hào)保持正確的操作順序。
但是大部分人覺得總是輸入(*p).i太麻煩了,因此C提供了一種簡(jiǎn)潔記法。下面的兩條語句完全等效,而第二條的輸入更加簡(jiǎn)便:
?
(*p).i=10; p->i=10;
閱讀別人代碼的時(shí)候,您會(huì)發(fā)現(xiàn)第二種記法比第一種更常用。
評(píng)論