一、前言
直到現在,我們已經知道了我們如何聲明常量類型,例如int,double,等等,還有複雜的例如數組和結構體等。我們聲明他們有各種語言的文法,例如Matlab,Python等等。在C語言中,把這些變數放在棧記憶體中。
二、基礎
1、棧
什麼是棧,它是你的電腦記憶體的一個特別地區,它用來儲存被每一個function(包括mian()方法)建立的臨時變數。棧是FILO,就是先進後出原則的結構體,它密切的被CPU管理和充分利用。每次function聲明一個新的變數,它就會被“推”到棧中。然後每次一個function退出時,所有關於這個函數中定義的變數都會被釋放(換句話說就是刪除)。一旦棧中的變數釋放,這塊地區就會變成可用的,提供給其他棧中的變數。
用棧儲存變數的好處是,記憶體是被你管理的。你不用手動的建立記憶體,不用當你不在需要它的時候手動釋放記憶體。另外,由於CPU組織棧記憶體很高效。讀出和寫入棧變數是很快的。
理解棧的關鍵是理解概念,當一個function退出時,所有它的變數都會從棧中彈出,以後都會永遠消失。因此棧中的變數本質是局部的。這和我們原來理解為變數範圍或者本地或者全域變數是相關的。在C中,一個公用的bug 是從你程式中的一個function外嘗試訪問一個在棧中的這個function的變數(在該function已經退出後)。
關於棧的另一個特點我們應該記住,就是儲存再棧中的變數的大小有限制。而堆上建立變數不用考慮。
總結棧:
a、棧的生長和伸縮就是函數壓入或者推出局部變數。
b、我們不用自己去管理記憶體,變數建立和釋放都是自動的。
c、棧中的變數只有在函數建立運行時存在。
2、 堆
堆也是我們的電腦記憶體中的一個地區,但是他不是自動管理的。而且也不是被CPU密切的管理著。它是一片更加自由的記憶體地區(很大)。要想在堆上建立記憶體,我們必須使用malloc() 或者calloc(),他們都是C語言編譯的。一旦你在堆上分配記憶體,當你不在需要的時候你必須用free()去銷毀。如果你不銷毀或者銷毀失敗,你的程式就會有記憶體泄露。換句話說就是堆記憶體會一直在,其他進程無法使用。我們將會再調試部分看到,那裡有一個叫做Valgrind的東西,它可以協助你發現記憶體泄露。
不像棧,堆沒有變數大小的限制(除了你電腦的物理限制條件外)。堆記憶體讀出和寫入都比較慢,因為它必須使用指標圖訪問堆記憶體。我們將會下面講解指標。
3、棧和堆的優缺點
棧:
a、快速存取。
b、沒有必要明確的建立分類變數,因為它是自動管理的。
c、空間被CPU高效地管理著,記憶體不會變成片段。
d、只有局部變數
e、受限於棧大小(取決於作業系統)
f、變數不能調整大小。
堆:
a、變數可以被全域訪問
b、沒有記憶體大小限制
c、(相對)訪問比較慢
d、沒有高效地使用空間,隨著塊記憶體的建立和銷毀,記憶體可能會變成片段。
e、你必須管理記憶體(變數的建立和銷毀你必須要負責)
f、變數大小可以用realloc( )調整
例如:
下面是一個在棧上建立變數的短程式。和我們看到的其他程式類似
#include <stdio.h> double multiplyByTwo (double input) { double twice = input * 2.0; return twice; } int main(int argc, const char * argv[]) { int age = 30; double salary = 12345.67; double myList[3] = {1.2,2.3,3.4}; printf("double your salary is %.3f\n",multiplyByTwo(salary)); return 0; }
運行結果如下: double your salary is 24691.340
在第7,8和9行,我們聲明了三個變數:一個int變數、一個double變數和一個包含三個包含double的數組。這三個變數在main()函數建立,被壓入棧中。當main()函數退出(程式退出),這些變數就會出棧。同樣地,在multiplyByTwo函數中,第二個double變數,也會在multiplyByTwo()函數建立的時候壓入棧中。一旦函數退出,第二個變數就會出棧,永遠地消失。
備忘:有一種方法可以告訴C保持一個棧變數。即使它的建立函數退出。那就是用static關鍵字當聲明變數的時候。一個變數用static關鍵之聲明,因此就會變成一個類似與全域變數的東西。但是它僅僅在建立它的函數裡面可見。這是一個奇怪的東西,除非你在一個非常特殊的情況下需要。
下面是另一個版本的建立變數在堆上而不是在棧上:
#include <stdio.h>#include <stdlib.h> double *multiplyByTwo (double *input) { double *twice = malloc(sizeof(double)); *twice = *input *2.0; return twice;}int main(int argc, const char * argv[]) { int *age = malloc(sizeof(int)); *age = 30; double *salary = malloc(sizeof(double)); *salary = 12345.67; double *myList = malloc(3 * sizeof(double)); myList[0] = 1.2; myList[1] = 3.4; myList[2] = 4.5; double *twiceSalary = multiplyByTwo(salary); printf("double your salary is %.3f\n",*twiceSalary); free(age); free(salary); free(myList); free(twiceSalary); return 0;}
正如你所看到的,我們用malloc()去分配堆記憶體,用free()去釋放它。這樣不是很大的處理,但是很笨重。還有一件要注意的事情是:這樣會由很多*號。這些是指標。malloc()(calloc()和free())函數處理的是指標而不是真正的數值。我們將會在下邊討論指標。指標在C棧是一個特殊的資料類型,它用來儲存記憶體的地址而不是儲存實際的values.因此在
這行,twice變數不是一個double,而是一個指向double的指標,是double被儲存再記憶體中的地址。
4、什麼時候使用堆
我們應該什麼時候使用堆和棧呢?如果我們需要分配一大塊記憶體(例如一個很大的數組或者一個很大的結構體),而且我們需要保持這個變數很長時間(例如全域變數)。我們應該分配堆記憶體。如果你處理的很小的變數,而且只要再函數使用的時候存活,那麼你應該使用棧,它比較方便而且快捷。如果你需要類似與數組或者結構體的變數,而且能夠動態改變大小(例如一個數組可以根據需要添加資料或者刪除資料),那麼你可以用malloc(),realloc()給他們分配堆記憶體,用free()手動的管理記憶體。當我們討論完指標,我們將會討論動態分配資料結構體。
通過以上對棧和堆的介紹,希望對大家瞭解和區分棧和堆有所協助。