C語言 動態儲存管理
為什麼需要動態儲存管理
程式中需要用變數(各種簡單型別變數、陣列變數等)儲存被處理的資料和各種狀態資訊,變數在使用之前必須安排好儲存:放在哪裡、佔據多少儲存單元,等等,這個工作被稱作儲存分配。用機器語言寫程式時,所有儲存分配問題都需要人處理,這個工作瑣碎繁雜、很容易出錯。在用高階語言寫程式時,人通常不需要考慮儲存分配的細節,主要工作由編譯程式在加工程式時自動完成。這也是用高階語言程式設計序效率較高的一個重要原因。
C程式裡的變數分為幾種。外部變數、區域性靜態變數的儲存問題在編譯時確定,其儲存空間的實際分配在程式開始執行前完成。程式執行中訪問這些變數,就是直接訪問與之對應的固定位置。對於區域性自動變數,在執行進入變數定義所在的複合語句時為它們分配儲存。應該看到,這種變數的大小也是靜態確定的。例如,區域性自動陣列的元素個數必須用靜態可求值的表示式描述。這樣,一個函式在呼叫時所需的儲存量(用於安放該函式裡定義的所有自動變數)在編譯時就完全確定了。函式定義裡描述了所需要的自動變數和引數,定義了陣列的規模,這些就決定了該函式在執行時實際需要的儲存空間大小。
以靜態方式安排儲存的好處主要是實現比較方便,效率高,程式執行中需要做的事情比較簡單。但這種做法也形成了對寫程式方式的一種限制,使某些問題在這個框架裡不好解決。舉個簡單的例子:假設現在要寫一個處理一組學生成績資料的程式,被處理資料需要儲存,因此應該定義一個數組。由於每次使用程式時要處理的成績的項數可能不同,我們可能希望在程式啟動後輸入一個表示成績項數的整數(或通過命令列引數提供一個整數,問題完全一樣)。對於這個程式,應該怎樣建立其內部的資料表示呢?
問題在於寫程式時怎樣描述陣列元素的個數。一種理想方式是採用下面的程式框架:
int n;
...
scanf("%d", &n);
double scores[n];
... /* 讀入成績資料,然後進行處理 */
但是這一做法行不通。這裡存在兩個問題:首先是變數定義不能出現在語句之後。這個問題好解決,可以引進一個複合語句,把scores的定義放在複合語句裡。第二個問題更本質,在上面程式段裡,描述陣列scores大小的表示式是一個變數,它無法靜態求出值。也就是說,這個陣列大小不能靜態確定,C語言不允許以這種方式定義陣列。這個問題用至今討論過的機制都無法很好解決。目前可能的解決方案有(一些可能性):
1. 分析實際問題,定義適當大小的陣列,無論每次實際需要處理多少資料都用這個陣列。前面的許多程式採用了這種做法。如果前期分析正確,這樣做一般是可行的。但如果某一次實際需要處理的資料很多,程式裡定義陣列不夠大,這個程式就不能用了(當然,除非使用程式的人有源程式,而且知道如果修改程式,如何編譯等等。在現實生活中,這種情況是例外)。
2. 定義一個很大的陣列,例如在所用的系統裡能定義的最大陣列。這樣做的缺點是可能浪費大量空間(儲存器是計算機系統裡最重要的一種資源)。如果在一個複雜系統裡,有這種情況的陣列不止一個,那就沒辦法了。如果都定義得很大,系統可能根本無法容納它們。而在實際計算中,並不是每個陣列都真需要那麼大的空間。
上面只是一個說明情況的例子。一般情況是:許多執行中的儲存需求在寫程式時無法確定。通過定義變數的方式不能很好地解決這類問題。為此就需要一種機制,使我們能利用它寫出一類程式,其中可以根據執行時的實際儲存需求分配適當大小的儲存區,以便存放到在執行中才能確定大小的資料組。C語言為此提供了動態儲存管理系統。說是“動態”,因為其分配工作完全是在動態執行中確定的,與程式變數的性質完全不同。程式裡可以根據需要,向動態儲存管理系統申請任意大小的儲存塊。
現在有了動態儲存分配,可以要求系統分配一塊儲存,但是怎麼能在程式裡掌握和使用這種儲存塊呢?對於普通的變數,程式裡通過變數名去使用它們。動態分配的儲存塊無法命名(命名是程式設計序時的手段,不是程式執行中可以使用的機制),因此需要另闢蹊徑。一般的語言裡都通過指標實現這種訪問,用指標指向動態分配得到的儲存塊(把儲存塊的地址存入指標),而後通過對指標的間接操作,就可以去使用儲存塊了。引用動態分配的儲存塊是指標的最主要用途之一。
|
4)分配調整函式realloc。函式原型是: void *realloc(void *p, size_t n); 這個函式用於更改以前的儲存分配。在呼叫realloc時,指標變數p的值必須是以前通過動態儲存分配得到的指標,引數n表示現在需要的儲存塊大小。realloc在無法滿足新要求時返回NULL,同時也保持p所指的儲存塊的內容不變。如果能夠滿足要求,realloc就返回一片能存放大小為n的資料的儲存塊,並保證該塊的內容與原塊一致:如果新塊較小,其中將存放著原塊裡大小為n的範圍內的那些資料;如果新塊更大,原有資料存在新塊的前面一部分裡,新增的部分不自動初始化。如果分配成功,原儲存塊的內容就可能改變了,因此不允許再通過p去使用它。 假如要把一個現有的雙精度塊改為能存放m個雙精度數,可以用下面程式段處理: q = (double *)realloc(p, m * sizeof(double)); if (q == NULL) { ... ... /* 分配不成功,p仍指向原塊,處理這種情況 */ } else { p = q; ... ... /* 分配成功,通過p可以去用新的儲存塊 */ } 上面的q是另一個雙精度指標。這裡沒有把realloc的返回值賦給直接p,是為了避免分配失敗時丟掉原儲存塊。如果直接賦值,指標p原來的值就會丟掉。如果當時的分配沒有成功,p將被賦空指標值,原來那個塊可能就再也找不到了(除非在這次調整前已經讓另一個指標指向了它)。 請注意:通過動態分配得到的塊是一個整體,只能作為一個整體去管理(無論是釋放還是改變大小)。在呼叫free(p) 或者realloc(p, ...) 時,p當時的值必須是以前通過呼叫儲存分配函式得到的,絕不能對指在動態分配塊裡其他位置的指標呼叫這兩個函式(更不能對並不指向動態分配塊的指標使用它們),那樣做的後果不堪設想。 三》 兩個程式例項 例:修改篩法程式,令它由命令列引數得到所需的整數範圍。如果沒有命令列引數,就要求使用者輸入一個確定範圍的整數值。 先考慮main的設計。為了使程式更加清晰,我們可以考慮把篩法計算寫成一個函式。這裡還有一個小問題:如果使用者通過命令列引數給出工作範圍,程式就需要從命令列引數字串計算出對應的整數。為此我們定義如下函式: int s2int(char s[]); 再利用原來的getnumber函式,這個程式的main可以定義為: enum { LARGEST = 32767 }; int main(int argc, char **argv) { int i, j, n, *ns; if (argc == 2) n = s2int(argv[1]); else getnumber("Largest number to test: ", 2, LARGEST, 5, &n); if (n < 2 || n > LARGEST) { printf("Largest number must in range [2, %d]", LARGEST); return 1; } if ((ns = (int*)malloc(sizeof(int)*(n+1))) == NULL) { printf("No enough memory!/n"); return 2; } sieve(n, ns); for(j = 1, i = 2; i <= n; ++i) if (ns[i] == 1) { printf("%7d%c", i, (j%8 == 7 ? '/n' : ' ')); ++j; } putchar('/n'); free(ns); return 0; } 主函式被清晰地分為三部分:準備工作,主要處理部分,輸出與結束。如果程式得到的範圍不合要求,它就列印錯誤資訊並立即結束。正常情況下完成篩法計算併產生輸出。 使用動態儲存管理的要點 1)必須檢查分配的成功與否。人們常用的寫法是: if ((p = (... *)malloc(...)) == NULL) { .. ... /* 對分配未成功情況的處理 */ } 2)系統對動態分配塊的使用不做任何檢查。程式設計序的人需要保證使用的正確性,絕不可以超出實際儲存塊的範圍進行訪問。這種越界訪問可能造成大災難。 3)一個動態分配塊的存在期並不依賴於分配這個塊的地方。在一個函式裡分配的儲存塊的存在期與該函式的執行期無關。函式結束時不會自動回收這種儲存塊,要回收這種塊,唯一的方法就是通過free釋放(完全由寫程式的人控制)。 |
4)如果在函式裡分配了一個儲存塊,並用區域性變數指向它,在這個函式退出前就必須考慮如何處理這個塊。如果這個塊已經沒用了,那麼就應該把它釋放掉;如果這個塊還有用(其中儲存著有用的資料),那麼就應該把它的地址賦給存在期更長的變數(例如全域性變數),或者把這個地址作為函式返回值,讓呼叫函式的地方去管理它。 5)其他情況也可能造成儲存塊丟失。例如給一個指向動態儲存塊的指標賦其他值,如果此前沒有其他指標指向這個塊,此後就再也無法找到它了。如果一個儲存塊丟失了,在這個程式隨後的執行中,將永遠不能再用這個儲存塊所佔的儲存。 6)計算機系統裡的儲存管理分很多層次。一個程式執行時,作業系統分給它一部分儲存,供它儲存程式碼和資料。其資料區裡包括一塊動態儲存區,由這個程式的動態儲存管理系統管理。該程式執行中的所有動態儲存申請都在這塊空間裡分配,釋放就是把不用的儲存塊交還程式的動態儲存管理系統。一旦這個程式結束,作業系統就會收回它佔用的所有儲存空間。所以,這裡說“儲存流失”是我們程式內部的問題,並不是整個系統的問題。當然,作業系統也是程式,它也有儲存管理問題,那是另一個層次的問題。 getnumber可以直接利用已有的定義(這裡又可以看到函式的價值),剩下的工作就是定義程式裡需要的兩個函式。從數字字串轉換產生整數的函式很簡單,它順序算出各數字字元的整數值並將其加入累加值,每處理一個數位都需要將原值乘10: int s2int(char s[]) { int n; for (n = 0; isdigit(*s); ++s) n = 10 * n + (*s - '0'); return n; } 在這個函式裡沒有檢查計算的溢位問題。如果需要,很容易加進這種檢查。這裡也可以直接用標準庫函式atoi,該函式完成的就是從數字字串到整數的轉換。有關atoi的情況請查閱本書第11章的介紹。 把篩法計算包裝為函式的工作很容易完成,下面是函式的定義: void sieve(int lim, int an[]) { int i, j, upb = sqrt(lim+1); an[0] = an[1] = 0; // 建立起初始向量 for (i = 2; i <= lim; ++i) an[i] = 1; for (i = 2; i <= upb; ++i) if (an[i] == 1) // i是素數 for (j = i*2; j <= lim; j += i) an[j] = 0; // 這些數都是i的倍數,因此不是素數 } 把這些函式定義(包括getnumber的定義)放到一起,適當安排函式位置,必要時加入原型。在原始檔前部加入適當 #include命令列,整個程式就完成了。 在這個程式裡需要儲存一批資料,但是資料的數目在寫程式時無法確定,因此只能採用動態儲存分配的方式。程式裡申請了一個大儲存塊,其中可以存放所需的int值。用指標指向這樣得到的儲存塊,用起來就像是在使用一個int陣列。 例:改造第6章的學生成績統計和直方圖生成程式,使之能處理任意多的學生成績。 本例的重點是討論一種常見問題的處理技術:通過動態分配的陣列,儲存事先完全無法確定數量的輸入資料。前一個例子是先確定了資料量,而後做一次動態分配。假如直到開始讀入資料的時候還不知道有多少資料項,那又該怎麼辦?下面我們解決這個問題。 在前面的成績直方圖程式用了一個數組,因此也限制了能處理的成績數。現在我們想修改readscores,由它全權處理輸入工作,在輸入過程中根據需要申請適當大小的儲存塊,把輸入資料存入其中。這樣,readscores結束時就需要返回兩項資訊:儲存資料的動態儲存塊地址,以及存於其中的資料項數。一個函式只能有一個返回值,另一“返回值”需要通過引數送出來。下面是修改後readscores的原型和main的定義: double* readscores(int* np); /*讀入資料,返回動態塊地址,通過np送回項數*/ |
|
|
相關推薦
C語言 動態儲存管理
為什麼需要動態儲存管理 程式中需要用變數(各種簡單型別變數、陣列變數等)儲存被處理的資料和各種狀態資訊,變數在使用之前必須安排好儲存:放在哪裡、佔據多少儲存單元,等等,這個工作被稱作儲存分配。用機器語言寫程式時,所有儲存分配問題都需要人處理,這個工作瑣碎繁雜、很容易出錯。在用
C語言動態記憶體管理malloc、calloc、realloc、free的用法和注意事項
此文是參考http://www.cplusplus.com/reference/cstdlib/裡的動態記憶體管理部分所寫,如發現有問題和不足之處,請參看原文,最好能幫忙指出,謝謝。 1.void* malloc (size_t size); malloc:分配一塊size
C語言動態記憶體管理:malloc、realloc、calloc以及free函式
我們已經掌握的記憶體開闢方式有: int val = 20;//在棧空間上開闢四個位元組 char arr[10] = {0};//在棧空間上開闢10個位元組的連續空間 但是這種開闢空間的方式有兩個特點: 1. 空間開闢的大小是固定的。
C語言動態記憶體管理和動態記憶體分配
動態記憶體管理同時還具有一個優點:當程式在具有更多記憶體的系統上需要處理更多資料時,不需要重寫程式。標準庫提供以下四個函式用於動態
資料結構之動態儲存管理(C語言)
一、 概述 1. 佔用塊 佔用塊:已分配給使用者使用的地址連續的記憶體區 可利用空間塊:未曾分配的地址連續的記憶體區 2. 動態儲存分配過程的記憶體狀態 系統執行一段時間後,有些程式的記憶體被釋放,造成了上圖(b)中的狀態。假如此時又有新
C語言記憶體的動態儲存管理2-空閒連結串列
空閒連結串列三種結構形式: (1)所有請求的記憶體大小相同。這是一種最簡單的動態儲存管理方式。 對此,系統通常的做法是: a)系統啟動時,將記憶體按大小均分成若干個塊,並形成一個連結串列。 b)分配時,只需將連結串列中第一個節點分配給使用者即可,無需掃描整個連結串列。 c)回
C語言中儲存類別、連結與記憶體管理
第12章 儲存類別、連結和記憶體管理 通過記憶體管理系統指定變數的作用域和生命週期,實現對程式的控制。合理使用記憶體是程式設計的一個要點。 12.1 儲存類別 C提供了多種不同的模型和儲存類別,在記憶體中儲存資料。 被儲存的每一個值都佔用一定的實體記憶體;C語言把這樣一塊記憶體稱為物件
C語言的儲存類別和動態記憶體分配
儲存類別分三大類: 靜態儲存類別 自動儲存類別 動態分配記憶體 變數、物件--->記憶體管理 記憶體考慮效率(時間更短、空間更小) 作用域 連結、---->空間 儲存器 ----->時間 其實儲存類別(時間、空間)和資料
c語言學生成績管理系統(可以將學生資訊儲存至txt檔案中)
程式截圖: 標頭檔案說明; 定義全域性變數; 定義、編寫輸入函式; 定義、編寫顯示函式; 定義、編寫修改函式; 定義、編寫查詢函式; 定義、編寫新增函式; 定義、編寫排序函式; 定義、編寫刪除函式; 定義、編
C語言、記憶體管理、堆、棧、動態分配
昨晚整理了一晚上居然沒了?沒儲存還是沒登入我也忘了,賊心累 我捋了捋,還是得從作業系統,程序和記憶體開始理解。 程序 從作業系統的角度簡單介紹一下程序。程序是佔有資源的最小單位,這個資源當然包括記憶體。在現代作業系統中,每個程序所能訪問的記憶體是互相獨立的(一些
【C 語言】記憶體管理 ( 動態記憶體分配 | 棧 | 堆 | 靜態儲存區 | 記憶體佈局 | 野指標 )
一. 動態記憶體分配 1. 動態記憶體分配相關概念 動態記憶體分配 : 1.C語言操作與記憶體關係密切 : C 語言中的所有操作都與記憶體相關 ; 2.記憶體別名 : 變數 ( 指標變數 | 普通變數 ) 和 陣
C語言動態內存的申請和釋放
== 否則 med 編程 nbsp 配對 強行 越界 初始化 什麽是動態內存的申請和釋放? 當程序運行到需要一個動態分配的變量時,必須向系統申請取得堆中的一塊所需大小的存儲空間,用於存儲該變量。當不再使用該變量時,也就是它的生命結束時,要顯式釋放它所占用的存儲
c++之動態記憶體管理
1.new/delete 和operator new/operator delete和malloc/free的關係 ①new呼叫operator new分配空間②new呼叫建構函式初始化物件。③delete呼叫解構函式清理物件 ④delete呼叫operator delete釋放空間 ⑤ope
C語言動態順序表的實現
上一次我們實現了靜態順序表,靜態順序表的大小固定,不方便我們去存取資料。 而動態順序表就可以很方便的存取資料。 同樣,我們有以下介面要實現: #ifndef __SEQLIST_H__ #define __SEQLIST_H__ #include<stdio.h> #include
C 語言動態記憶體
文章目錄 說明 記憶體示意圖 alloc() malloc() calloc() realloc() free() 常見錯誤程式碼例項 說明 主要參考以下部落格:
c語言動態分配記憶體及記憶體分配部分函式
#include<stdio.h> /** 在C中動態分配記憶體的基本步驟有: 1,用malloc類的函式分配記憶體; 2,用這些記憶體支援應用程式 3,用free函式釋放記憶體 二、動態記憶體分配函式 malloc :從堆上分配記憶體 &nbs
c語言動態分配空間
問題: typedef struct node{ int num; struct node*next; }Node,*pNode 在連結串列的create函式中,為什麼定義了連結串列頭之後,以後的每個空間都要new(c++中的用法)或
C語言動態記憶體學習筆記
一、malloc返回引數有兩種情況 1,當分配的記憶體池是空的時候返回一個NULL指標。 2,當可用記憶體無法滿足要求,malloc向作業系統請求,要求更多記憶體,如果它無法向malloc提供更多記憶體就返回一個NULL指標 二、free的引數 free的引數必須是NULL或mall
C語言(記憶體管理、檔案處理)
記憶體的理解 計算機記憶體是以位元組為單位進行儲存,每個位元組都有自己的編號即地址(指標)。 本圖為原始碼 其中01 00 00 00 中的兩個連在一起的數為一個位元組,0x00FAFB7C是01的地址,之後的三個位元組的地址值分別遞增1 上圖中,num[3]為int
嵌入式C語言之深度解讀C語言的儲存域,作用域,生命週期,連結屬性
***儲存類: 就是儲存型別,描述,C語言變數的儲存地址。 記憶體的管理方式:棧 堆 資料段 bss段 .text段。 一個變數的儲存型別就是描述這個變數儲存在何種記憶體段之