首先,回顧一些有關內存分配的事實。全部的程序都必須留出足夠內存來存儲它們使用的數據。一些內存分配是自動完成的。例如,能夠這樣聲明:程序員
float x;編程
char place[]="Dancing oxen creek";數組
因而,系統將留出存儲float或字符串的足夠內存空間,您也能夠更明確地請求確切數量的內存:函數
int plates[100];工具
這個聲明留出100個內存位置,每一個位置可存儲一個int值。在全部這些情形中,聲明同時給出了內存的標識符,所以您可使用x或place來標識數據。ui
C的功能還不止這些。能夠在程序運行時分配 更多的內存。主要工具是函數malloc(),它接受一個參數:所需內存字節數。而後,malloc()找到可用內存中一個大小合適的塊。內存是匿名的,也就是malloc()分配了內存,但沒有爲它指定名字。然而,它卻能夠返回那塊內存第一個字節的地址。所以,您能夠把那個地址賦給一個指針變量,並使用該指針來訪問那塊內存。由於char表明一個字節,因此傳統上曾將malloc()定義爲指向char的指針類型。然而,ANSI C 標準使用了一個新類型:指向void的指針。這一類型被用做「通用指針」。函數malloc()可用來返回數組指針、結構指針等等,所以通常須要把返回值的類型指派爲適當的類型。在ANSI C 中,爲了程序清晰應對指針進行類型指派,但將void指針值賦給其餘類型的指針並不構成類型衝突。若是malloc()找不到所需的空間,它將返回空指針 。spa
咱們使用malloc()來建立一個數組。能夠在程序運行時使用malloc()請求一個存儲塊,另外還須要一個指針來存放該塊在內存中的位置。例如,考慮以下代碼:操作系統
double * ptd;指針
ptd = (double *)malloc(30*sizeof(double));code
這段代碼請求30個double類型值的空間,而且把ptd指向該空間所在的位置。注意,ptd是做爲指向一個double類型值的指針聲明的,而不是指向30個double類型值的數據塊的指針。記住:數組的名字是它第一個元素的地址。所以,若是您令ptd指向一個內存塊的第一個元素,就能夠像使用數組名同樣使用它。也就是說,可使用表達式ptd[0]來訪問內存塊的第一個元素,ptd[1]來訪問第二個元素,依此類推。正如前面所學,能夠在指針符號中使用數組名,也能夠在數組符號中使用指針。
如今,建立一個數組有三個方法:
一、聲明一個數組,聲明時用常量表達式指定數組,而後能夠用數組名訪問數組元素。
二、聲明一個變長數組,聲明時用變量表達式指定數組,而後用數組名來訪問數組元素(回憶下,這是C99的特性)。
三、聲明一個指針,調用malloc(),而後使用該指針來訪問數組元素。
使用第二種或第三種方法能夠作一些用普通的數組聲明作不到的事。建立一個動態數組(dynamic arry)即一個在程序運行時才分配內存並可在程序運行時選擇大小的數組。例如,假定n是一個整數變量。在c99以前,不能這樣作:
double item[n]; /*若是n 是一個變量,C99以前不容許這樣作*/
然而,即便在C99以前的編譯器中,也能夠這樣作:
ptd = (double *)malloc(n * sizeof(double)); /*能夠*/
這行得通,並且正如您看到的那樣,這樣作比使用一個變長數組更加靈活。
通常地,對應每一個malloc()調用,應該調用一次free()。函數free()的參數是先前malloc()返回的地址,它釋放先前分配的內存。這樣,所分配內存的持續時間從調用malloc()分配內存開始,到調用 free()釋放內存以供再使用爲止。設想malloc()和free()管理着一個內存池。每次調用 malloc()分配內存給程序使用,每次調用free()將內存歸還到池中,使內存可被再次使用。free()的參數應是一指針,指向由malloc()分配的內存塊;不能使用free()來釋放經過其餘方式(例如聲明一個數組)分配的內存。在頭文件stdlib.h中有malloc()和free()的原型。
經過使用malloc(),程序能夠在運行時決定須要多大的數組並建立它。程序清單12.14舉例證實了這一可能。它把內存塊地址賦給指針ptd,接着以使用數組名的方式使用ptd。程序還調用了exit()函數。該函數的原型在stdlib.h中,用來在內存分配失敗時結束程序。值EXIT_FAILURE也在這個頭文件中定義。標準庫提供了兩個保證可以在全部操做系統下工做的返回值:EXIT_SUCCESS(或者等同於0)指示程序正常終止,EXIT_FAILURE指示程序異常終止。
程序清單12.14 dyn_arr.c程序
/*dyn_arr.c -- 爲數組動態分配存儲空間*/ #include <stdio.h> #include <stdlib.h> //爲malloc()和free()函數提供原型 int main(void) { double * ptd; int max; int number; int i = 0; puts("What is the maximum number of type double entries?"); scanf("%d",&max); ptd = (double *)malloc(max*sizeof(double)); if(ptd == NULL) { puts("Memory allocation failed.Goodbye."); exit(EXIT_FAILURE); } /*ptd如今指向有max個元素的數組*/ puts("Enter the values(q to quit):"); while(i < max && scanf("lf%",&ptd[i]) == 1) ++i; printf("Here are your %d entries: \n",number=i); for(i = 0; i<number;i++) { printf("%7.2f",ptd[i]); if(i%7 == 6) putchar('\n'); } if(i%7 != 0) putchar('\n'); puts("Done."); free(ptd); return 0; }
運行示例:
What is the maximum number of entries? 5 Enter the values (q to quit): 20 30 35 25 40 80 Here are your 5 entries: 20.00 30.00 35.00 25.00 40.00 Done.
來看一下代碼。程序經過下列幾行獲取所需的數組的大小:
puts ("What is the maximum number of type double entries?");
scanf("%d",&max);
接着,下面的行分配對於存放所請求數目的項來講足夠大的內存,並將該內存塊的地址賦給指針ptd:
ptd = (double *) malloc( max * sizeof(double));
在C中類型指派(double *)是可選的,而在C++中必須有,所以使用類型指派將C移植到C++更容易。
malloc()可能沒法得到所需數量的內存。在那種情形下,函數返回空指針,程序終止。
if(ptd == NULL)
{
puts("Memory allocation failed.Goodbye.");
exit(EXIT_FAILTURE);
}
若是成功地分配了地址,程序將把ptd視爲一個具備max個元素的數組的名字。
在這個特定的例子中,使用free()不是必須的,由於在程序終止後全部已分配的內存都將被釋放。然而在一個更加複雜的程序中,可以釋放並再利用內存將是重要的。
使用動態數組將得到什麼?主要是得到了程序的靈活性。您可使用動態數組來使程序適應不一樣的情形。
12.6.1 free()的重要性
在編譯程序時,靜態變量的數量是固定的,在程序運行時也不改變。自動變量使用的內存數量在程序運行時自動增長或者減小。但被分配的內存所使用的內存數量只會增長,除非您記得使用free()。例如,假定有一個以下代碼勾勒出的函數,它建立一個數組的臨時拷貝:
... int main() { double glad[2000]; int i; ... for(i=0; i<1000; i++) gobble(glad,2000); ... } void gobble(double ar[],int n) { double * temp = (double *) malloc(n*sizeof(double)); ... /*free(temp); //忘記使用free()*/ }
假定咱們如暗示的那樣沒有使用freee()。當函數終止時,指針temp做爲一個自動變量消失了。但它所指向的16000個字節的內存仍舊存在。咱們沒法訪問這些內存,由於地址不見了。因爲沒有調用free(),不能夠再使用它了。
第二次調用gobble(),它又建立了一個temp,再次使用malloc()分配 16000個字節的內存。第一次16000字節的塊已不可用,所以malloc()不得再也不找一個16000字節的塊。當函數終止時,這個內存塊也沒法訪問,不可再利用。
但循環執行 1000次,所以在循環最終結束時,已經有1600萬字節的內存從內存池中移走。事實上,到達這一步以前,程序極可能已經內存溢出了。這類問題被稱爲「內存泄漏(memory leak),能夠經過在函數末尾處調用 free()防止該問題出現。
12.6.2 函數calloc()
內存分配還可使用calloc()。典型的應用以下:
long * newmem;
newmem = (long *)calloc(100,sizeof(long));
與malloc()相似,calloc()在ANSI C之前的版本中返回一個char指針,在ANSI 中返回一個void指針。若是要存儲不一樣類型,應該使用類型指派運算符。這個新函數接受兩個參數,都應是無符號的整數(在ANSI 中 是SIZE_T類型)。第一個參數是所需內存單元的數量,第二個參數是每一個單元以字節計的大小。在這裏,long使用4個字節,所以這一指令創建了100個4字節單元,總共使用400個字節來存儲。
使用sizeof(long)而不是使用4使代碼更容易移植。它能夠其餘系統中運行, 這些系統 中long不是4字節而是別的大小 。
函數calloc()還有一個特性:它將塊中的所有位都置爲0(然而要注意,在某些硬件系統中,浮點值0不是用所有位爲0來表示的)。
函數free()也能夠用來釋放由calloc()分配的內存。
動態內存分配是不少高級編程技巧的關鍵。在17章「高級數據表示」中咱們將研究一些。你本身的C庫可能提供了其餘內存管理函數,有些可移植,有些不能夠。您可能應該抽時間看一下。
12.6.3 動態內存分配與變長數組
變長數組(Variable-Length Array,VLA)與malloc()在功能上有些一致。例如,它們均可以用來建立一個大小在運行時決定的數組:
int vlamal() { int n; int * pi; scanf("%d",&n); pi = (int *) malloc(n*sizeof(int)); int ar[n]; //變長數組 pi[2] = ar[2] =-5; ... }
一個區別 在於VLA是自動存儲的。自動存儲的結果之一就是VLA所用內存空間在運行完定義部分以後 會自動釋放。在本例中,就是函數vlamal()終止的時候。所以沒必要使用free()。另外一方面,使用由malloc()建立的數組沒必要侷限在一個函數中。例如,函數能夠建立一個數組並返回指針,供調用該函數的函數訪問。接着,後者能夠在它結束時調用free()。free()可使用不一樣於malloc()指針的指針變量,必須一致的是指針中存儲的地址。
VLA對多維數組來講更方便。您可使用malloc()來定義一個二維數組,但語法很麻煩。若是編譯器不支持VLA特性,必須固定一維的大小,正以下面的函數調用 :
int n=5; int m=6; int ar2[n][m]; //n*m的變長數組 int (* p2)[6]; //在C99以前可使用 int (* p3)[m]; //要求變長數組支持 p2 = (int (*)[6])malloc(n*6*sizeof(int)); //n*6數組 p3 = (int (*)[m])malloc(n*m*sizeof(int)); //n*m數組 //上面的表達式也要求變長數組支持 ar2[1][2] = p2[1][2] = 12;
有必要查看一下指針聲明。函數malloc()返回一個指針,所以p2必須是適當類型的指針。下面的聲明:
int (*p2)[6]; //在C99以前可使用
代表p2指向一個包含6個int值的數組。這意味着p2[i]將被解釋爲一個由6個整數構成的元素,p2[i][j]將是一個int 值。
第二個指針聲明使用變量來指定p3所指數組的大小。這意味着p3將被看做一個指向VLA的指針,這正是代碼不能在C90標準中運行的緣由。
12.6.4 存儲類與動態內存分配
您可能正在爲存儲類和動態內存分配之間的聯繫感到疑惑。咱們來看一個理想模型。能夠認爲程序將它的可用內存分紅 了三個獨立的部分:一個是具備外部連接的、具備內部連接的以及具備空連接的靜態變量的;一個是自動變量的;另外一個是動態分配的內存的。
在編譯時就已經知道 了靜態存儲時期存儲類變量所需的內存數量,存儲在這一部分的數據在整個程序運行期間均可以用。這一類型的每一個變量在程序開始已經存在,到程序結束時終止。
然而,一個自動變量在程序進入包含該變量定義的代碼產生,在退出這一代碼塊時終止 。所以,伴隨着程序對函數的調用和終止,自動變量使用的內存數量也在增長和減小。典型的,將這一部份內存處理爲一個堆棧。這意味着在內存中,新變量在建立時按順序加入,在消亡時按相反順序移除。
動態分配的內存在調用malloc()或相關函數時產生,在調用free()時釋放。由程序員而不是一系列固定的規則控制內存持續時間,所以內存塊可在一個函數中建立,而在另外一個函數中釋放。因爲這一點,動態內存分配所用的內存部分可能變成碎片狀,也就是說,在活動的內存塊之間散佈着未使用的字節片。
無論怎樣,使用動態內存每每致使進程比使用堆棧內存慢。