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