回到「13. 額外語法 (Extra syntax)」
上一頁
名詞解釋:
「釋放」記憶體:將某塊記憶體標記為「使用完畢」,讓這塊記憶體之後或讓其他程式可以再被利用
在之前的字元字串章節,我們使用了一個很大很大 (大小為100萬) 的陣列來儲存「一個名字」
除非要處理特別多的資料,否則應該是不會需要這麼大的陣列啦XD
但接下來,您會發現,當陣列太大的時候,會發生問題的
為了節省版面,這裡以字元字串章節的最後一份程式碼為例:
#include<cstdio> int main() { char name[1000001]; scanf("%s",name); printf("Hello, %s!\n",name); return 0; }
輸入名字「Motivation」,會輸出「Hello, Motivation!」 |
假如哪天你發現某人的名字太長了 (?),大小100萬的陣列不夠用 (!),想要宣告大小1000萬的陣列 (......)
好啊!欣然同意啊XD
#include<cstdio> int main() { char name[10000001]; scanf("%s",name); printf("Hello, %s!\n",name); return 0; }
欸欸等等我甚麼是都還沒做耶! |
咦?怎麼程式掛了?!
根本都還沒開始輸入名字呀!
仔細想想,在輸入名字之前,程式只有執行了一行:char name[10000001];
而我們剛剛的修改也只是把陣列的長度多加一個0而已
What's the problem???
陣列太大導致記憶體不足嗎?
不對啊,仔細算算,我們才用了「10000001 Bytes = 9765.6 KB = 9.5 MB」的記憶體啊
相對電腦隨隨便便就有幾GB (1 GB = 1024 MB) 的閒置記憶體,根本一點都不成問題!
其實,問題不在你身上,是電腦程式本身架構的問題!
一個用C++寫出來的電腦程式使用的記憶體,其實是有分類的
大概可以分成三類:
1. static (靜態)
2. heap (堆積)
3. stack (堆疊)
-
「static記憶體」的特性是程式執行之前會先配置好哪一塊記憶體要用來幹嘛,之後就不再容許更改,適合儲存從頭到尾都要存在的資料
例如:程式的邏輯和架構,以後甚至會學到怎麼把一些變數自訂成使用static記憶體 -
「heap記憶體」的特性是可以在程式執行的過程中隨時取得和釋放,專業上我們稱之可以「動態配置」記憶體。不過機制較為複雜,記憶體的取得和釋放也較為耗時
通常用來儲存巨量的快取資料 (也就是cache,目的在於解決從硬碟讀取太慢的問題)、或者程式執行過程中臨時需要大量存放的資料 (暫存資料) - 「stack記憶體」的特性是會隨著函式的呼叫而使用,在函式內宣告的變數通常會使用stack記憶體。機制簡單,記憶體的取得和釋放很快,但也因為這個機制的缺陷,分配多點記憶體的投資報酬率並不高,電腦並不喜歡提供stack記憶體太多額度 (通常只有4 MB左右)
答案是「stack」,也就是被電腦限制使用量的那種
因此,當我們在函式裡面宣告一個很大很大的陣列,很容易就會造成「stack overflow (堆疊溢出)」的情況,也就是使用的stack記憶體超出電腦提供的額度而讓程式發生錯誤
太不公平了吧!整台電腦4 GB的記憶體我們只能使用4 MB?!
別忘了,除了「stack記憶體」之外,我們還有「static記憶體」和「heap記憶體」可以使用
「static記憶體」在使用上有些限制和特色,之後的頁面會介紹
那麼,要怎麼使用「heap記憶體」呢?
請注意,這會需要熟悉指標的用法,對指標還不熟的同學們,請複習一下指標章節
在上一頁,我們得知指標就是陣列
因此,我們只要和電腦取得某一塊可用heap記憶體的「指標」,就可以用「陣列」的方式使用heap記憶體
例如,跟電腦要一塊大小為「10000000個char變數」的heap記憶體,並取得其指標的方式如下:
new char[10000000]
這整個東西可以當作一個char* (char的指標),因此我們可以用一個型別為「char*」的變數 (就命名為「name」吧) 來儲存它:
char *name=new char[10000000];
可以發現,現在我們可以直接把「name」當作一個「長度為10000000的char陣列」來用了!
而且使用的是「heap記憶體」,不用理會那可憐的「4 MB」限制!
好了,現在,您知道要怎麼解決本頁開頭程式掛掉的問題了嗎?
希望您可以自行解決此問題,但還是提供解答以供參考:
#include<cstdio> int main() { char *name=new char[10000001]; scanf("%s",name); printf("Hello, %s!\n",name); return 0; }
程式正常執行了耶! |
等等,事情沒有這麼簡單!這份程式碼還是有問題的!
heap記憶體的取得和釋放都很耗時,而且多個指標使用同一塊記憶體是允許的,因此就算程式執行到「name的作用範圍」之外,電腦還是不會自動釋放「name」使用的那塊記憶體
好處是,如果有另一個char*指標 (假設叫做「a」) 和name使用了同一塊記憶體,那麼就算執行到了name的作用範圍之外,我們還是可以透過「a」(如果還在a的作用範圍內) 來繼續使用name使用的那塊記憶體和裡面儲存的資料
舉個例子:
#include<cstdio> int main() { char *a; { char *name=new char[10000001]; scanf("%s",name); printf("name: Hello, %s!\n",name); a=name; } printf("a: Hello, %s!\n",a); return 0; }
我們將「name指標」的資訊 (記憶體編號) 複製給「a指標」
當程式執行到「name」的作用範圍外時,還是可以透過「a」來取得之前「name」使用的那一塊記憶體的資訊:Motivation
|
電腦並不會自動幫您釋放記憶體,意味著如果您沒有針對這件事做任何處理,程式在全部執行完畢之前,只會無可救藥地消耗越來越多的記憶體 (因為記憶體沒有被釋放就無法被重新利用),這種情況我們稱之「記憶體泄漏 (memory leak)」
如果您的程式執行地夠久 (依據您程式的記憶體使用速率而定),最終一定會將整台電腦的記憶體消耗殆盡
這會是一個很大很大的問題,如果您的作業系統沒有特別去偵測這種情況 (偵測這種東西是不容易的),會有那麼一個時間點,螢幕畫面突然從此停止不動了!
到時候,強制將電源線拔掉並重新開機將是您唯一的選項
如何避免這種問題呢?
到時候,強制將電源線拔掉並重新開機將是您唯一的選項
如何避免這種問題呢?
首先,您必須學會怎麼手動「釋放」您的程式先前跟電腦要的heap記憶體
方式如下 (以本頁範例中的「name」為例):
就這麼一行,小動作可以解決大問題!
方式如下 (以本頁範例中的「name」為例):
delete[] name;
就這麼一行,小動作可以解決大問題!
因此,程式碼應該要這樣寫:
#include<cstdio> int main() { char *name=new char[10000001]; scanf("%s",name); printf("Hello, %s!\n",name); delete[] name; return 0; }
什麼,您說想看看加不加delete的差異?
沒問題!
沒問題!
這裡提供一個例子:
#include<cstdio> int main() { for(int i=0;i<100;i++) { char *name=new char[500000001]; name[0]='M'; name[1]='o'; name[2]='t'; name[3]='i'; name[4]='v'; name[5]='a'; name[6]='t'; name[7]='i'; name[8]='o'; name[9]='n'; name[10]='\0'; printf("i = %d, name = %s\n",i,name); delete[] name; } return 0; }
可以看到,程式順利地執行完畢了!
現在,請將有delete的那一行程式碼移除,再執行一次程式看看吧~
#include<cstdio> int main() { for(int i=0;i<100;i++) { char *name=new char[500000001]; name[0]='M'; name[1]='o'; name[2]='t'; name[3]='i'; name[4]='v'; name[5]='a'; name[6]='t'; name[7]='i'; name[8]='o'; name[9]='n'; name[10]='\0'; printf("i = %d, name = %s\n",i,name); } return 0; }
祝您一路好走,就不送囉~XD
對了,請記得先把重要資料存檔!!!
沒有留言:
張貼留言
歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原
注意:只有此網誌的成員可以留言。