簡介,對比分析,堆疊空間分配,堆疊快取方式,堆疊數據結構區別,區別介紹,java,C/C++,理論知識,申請方式,申請回響,申請限制,效率比較,存儲內容,存取比較,小結,主要分別,補充說明,
簡介 單片機套用中,堆疊是個特殊存儲區,堆疊屬於RAM空間的一部分,堆疊用於函式調用、中斷切換時保存和恢復現場數據。堆疊中的物體具有一個特性:第一個放入堆疊中的物體總是被最後拿出來, 這個特性通常稱為先進後出 (FILO—First-In/Last-Out)。 堆疊中定義了一些操作, 兩個最重要的是PUSH和POP。 PUSH(入棧)操作:堆疊指針(SP)加1,然後在堆疊的頂部加入一 個元素。POP(出棧)操作相反,出棧則先將SP所指示的內部ram單元中內容送入直接地址定址的單元中(目的位置),然後再將堆疊指針(SP)減1.。這兩種操作實現了數據項的插入和刪除。
對比分析 堆疊空間分配 堆(作業系統):由作業系統自動分配釋放 ,存放函式的
參數值 ,
局部變數 的值等。其操作方式類似於數據結構中的棧。
棧(作業系統): 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收,分配方式倒是類似於鍊表。
堆疊快取方式 棧使用的是
一級快取 , 他們通常都是被調用時處於存儲空間中,調用完畢立即釋放。
堆則是存放在
二級快取 中,生命周期由虛擬機的垃圾回收算法來決定(並不是一旦成為孤兒對象就能被回收)。所以調用這些對象的速度要相對來得低一些。
堆疊數據結構區別 堆(數據結構):堆可以被看成是一棵樹,如:堆排序。
棧(數據結構):一種先進後出的數據結構。
例如:順序棧AStack的類定義
template < class T >
class AStack {
private:
int size ; // 數組的規模
T * stackArray ; // 存放堆疊元素的數組
int top ; // 棧頂所在數組元素的下標
public:
AStack ( int MaxStackSize ) // 構造函式
{ size = MaxStackSize ; stackArray = new T [MaxStackSize] ; top = -1 ; }
~AStack ( ) { delete [ ] stackArray ; } // 析構函式
bool Push ( const T& item ) ; // 向棧頂壓入一個元素
bool Pop ( T & item ) ; // 從棧頂彈出一個元素
bool Peek ( T & item ) const ; // 存取棧頂元素
int IsEmpty ( void ) const { return top = = -1 ; }
// 檢測棧是否為空
int IsFull ( void ) const { return top size-1 ; }
// 檢測棧是否為滿
void clear ( void ) { top-1 ; } // 清空棧
} ;
區別介紹 java 1. 棧(stack)與堆(heap)都是
Java 用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程式設計師不能直接地設定棧或堆。
2. 棧的優勢是,存取速度比堆要快,僅次於直接位於CPU中的
暫存器 。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。另外,棧數據在多個執行緒或者多個棧之間是不可以共享的,但是在棧內部多個值相等的變數是可以指向一個地址的,詳見第3點。堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態分配記憶體,存取速度較慢。
3.Java中的數據類型有兩種。
一種是基本類型(primitivetypes), 共有8種,即int,short, long, byte, float, double, boolean, char(注意,並沒有string的基本類型)。這種類型的定義是通過諸如int a= 3; long b = 255L;的形式來定義的,稱為
自動變數 。值得注意的是,自動變數存的是字面值,不是類的實例,即不是類的引用,這裡並沒有類的存在。如int a= 3; 這裡的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,由於大小可知,生存期可知(這些字面值固定定義在某個程式塊裡面,程式塊退出後,欄位值就消失了),出於追求速度的原因,就存在於棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:
編譯器先處理int a= 3;首先它會在棧中創建一個變數為a的記憶體空間,然後查找有沒有字面值為3的地址,沒找到,就開闢一個存放3這個字面值的地址,然後將a指向3的地址。接著處理int b= 3;在創建完b的引用變數後,由於在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。
特別注意的是,這種字面值的引用與類對象的引用不同。假定兩個類對象的引用同時指向一個對象,如果一個對象引用變數修改了這個對象的內部狀態,那么另一個對象引用變數也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導致另一個指向此字面值的引用的值也跟著改變的情況。如上例,我們定義完a與b的值後,再令a=4;那么,b不會等於4,還是等於3。在編譯器內部,遇到a=4;時,它就會重新搜尋棧中是否有4的字面值,如果沒有,重新開闢地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
另一種是包裝類數據,【如Integer,String, Double等將相應的基本數據類型包裝起來的類。這些類數據全部存在於【堆】中】,Java用new()語句來顯示地告訴編譯器,在運行時才根據需要動態創建,因此比較靈活,但缺點是要占用更多的時間。 4.String是一個特殊的包裝類數據。即可以用String str = new String("abc");的形式來創建,也可以用String str = "abc";的形式來創建(作為對比,在JDK 5.0之前,你從未見過Integer i = 3;的表達式,因為類與字面值是不能通用的,除了String。而在JDK5.0中,這種表達式是可以的!因為
編譯器 在後台進行Integer i = new Integer(3)的轉換)。前者是規範的類的創建過程,即在Java中,一切都是對象,而對象是類的實例,全部通過new()的形式來創建。Java中的有些類,如DateFormat類,可以通過該類的getInstance()方法來返回一個新創建的類,似乎違反了此原則。其實不然。該類運用了單例模式來返回類的實例,只不過這個實例是在該類內部通過new()來創建的,而getInstance()向外部隱藏了此細節。那為什麼在String str = "abc";中,並沒有通過new()來創建實例,是不是違反了上述原則?其實沒有。
4. 關於String str = "abc"的內部工作。Java內部將此語句轉化為以下幾個步驟:【String str = "abc",String str不要連著】
(1)先定義一個名為str的對String類的對象引用變數:String str;
(2)【在【棧】中查找有沒有存放值為"abc"的地址,如果沒有,則開闢一個存放字面值為"abc"的地址,接著創建一個新的String類的對象o,並將o的字元串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象o。如果已經有了值為"abc"的地址,則查找對象o,並返回o的地址。】【上文說數據時存放在堆中,此文說數據存放在棧中】[因為此處不是通過new()創建的啊]
(3)將str指向對象o的地址。
值得注意的是,一般String類中字元串值都是直接存值的。但像String str = "abc";這種場合下,其字元串值卻是保存了一個指向存在棧中數據的引用!
為了更好地說明這個問題,我們可以通過以下的幾個代碼進行驗證。 複製內容到剪貼簿代碼:
String str1="abc";String str2="abc";System.out.println(str1==str2);//true 注意,我們這裡並不用str1.equals(str2);的方式,因為這將比較兩個字元串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值。而我們在這裡要看的是,str1與str2是否都指向了同一個對象。
結果說明,
JVM 創建了兩個引用str1和str2,但只創建了一個對象,而且兩個引用都指向了這個對象。
我們再來更進一步,將以上代碼改成: 複製內容到剪貼簿代碼:
String str1="abc";String str2="abc";str1="bcd";System.out.println(str1+","+str2);//bcd,abcSystem.out.println(str1==str2);//false 這就是說,賦值的變化導致了類對象引用的變化,str1指向了另外一個新對象!而str2仍舊指向原來的對象。上例中,當我們將str1的值改為"bcd"時,JVM發現在棧中沒有存放該值的地址,便開闢了這個地址,並創建了一個新的對象,其字元串的值指向這個地址。
事實上,String類被設計成為不可改變(immutable)的類。如果你要改變其值,可以,但JVM在運行時根據新值悄悄創建了一個新對象,然後將這個對象的地址返回給原來類的引用。這個創建過程雖說是完全自動進行的,但它畢竟占用了更多的時間。在對時間要求比較敏感的環境中,會帶有一定的不良影響。
再修改原來代碼: 複製內容到剪貼簿代碼:
String str1="abc";String str2="abc";str1="bcd";String str3=str1;System.out.println(str3);//bcdString str4="bcd";System.out.println(str1==str4);//true 我們再接著看以下的代碼。 複製內容到剪貼簿代碼: String str1 = new String("abc"); String str2 = "abc"; System.out.println(str1==str2); //false
String str1 = "abc"; String str2 = new String("abc"); System.out.println(str1==str2); //false 創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象。
以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中創建,而且其字元串是單獨存值的,即使與棧中的數據相同,也不會與棧中的數據共享。
5. 數據類型包裝類的值不可修改。不僅僅是String類的值不可修改,所有的數據類型包裝類都不能更改其內部的值。
6. 結論與建議:
(1)我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,我們創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!唯一可以肯定的是,指向String類的引用被創建了。至於這個引用到底是否指向了一個新的對象,必須根據上下文來考慮,除非你通過new()方法來顯要地創建一個新的對象。因此,更為準確的說法是,我們創建了一個指向String類的對象的引用變數str,這個對象引用變數指向了某個值為"abc"的String類。清醒地認識到這一點對排除程式中難以發現的bug是很有幫助的。
(2)使用String str = "abc";的方式,可以在一定程度上提高程式的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對於Stringstr = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字元串值是否相等,是否有必要創建新對象,從而加重了程式的負擔。這個思想應該是享元模式的思想,但JDK的內部在這裡實現是否套用了這個模式,不得而知。
(3)當比較包裝類裡面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==。
(4)由於String類的immutable性質,當String變數需要經常變換其值時,應該考慮使用StringBuffer類,以提高程式效率
C/C++ 一個由C/C++編譯的程式占用的記憶體分為以下幾個部分
1、棧區(stack)— 由
編譯器 自動分配釋放 ,存放函式的參數名,
局部變數 的名等。其操作方式類似於數據結構中的棧。
2、堆區(heap)— 由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似於
鍊表 。
3、靜態區(static)—
全局變數 和局部
靜態變數 的存儲是放在一塊的。程式結束後由系統釋放。
4、文字常量區—常量字元串就是放在這裡的,程式結束後由系統釋放 。
變數的存儲方式
存儲描述
持續性
作用域
連結性
如何聲明
自動
自動
代碼塊
無
在代碼塊中
暫存器
自動
代碼塊
無
在代碼塊中,使用關鍵字 register
靜態,無連結性
靜態
代碼塊
無
在代碼塊中,使用關鍵字 static
靜態,外部連結性
靜態
檔案
外部
不在任何函式內
靜態,內部連結性
靜態
檔案
內部
不在任何函式內,使用關鍵字 static
首先,定義靜態變數時如果沒有初始化編譯器會自動初始化為0.。接下來,如果是使用常量表達式初始化了變數,則編譯器僅根據檔案內容(包括被包含的頭檔案)就可以計算表達式,編譯器將執行常量表達式初始化。必要時,編譯器將執行簡單計算。如果沒有足夠的信息,變數將被動態初始化。請看一下代碼:
int global_1=1000;//靜態變數外部連結性常量表達式初始化int global_2;//靜態變數外部連結性零初始化static int one_file_1=1000;//靜態變數內部連結性常量表達式初始化static int one_file_2;//靜態變數內部連結性零初始化int main(){static int count_1=1000;//靜態變數無連結性常量表達式初始化static int count_2;//靜態變數無連結性零初始化return 0;} 所有的靜態持續變數都有下述初始化特徵:未被初始化的靜態變數的所有位都被設為0。這種變數被稱為零初始化。以上代碼說明關鍵字static的兩種用法,但含義有些不同:用於局部聲明,以指出變數是無連結性的靜態變數時,static表示的是存儲持續性;而用於代碼塊外聲明時,static表示內部連結性,而變數已經是靜態持續性了。有人稱之為關鍵字重載,即關鍵字的含義取決於上下文。
理論知識 申請方式 stack:
由系統自動分配。 例如,聲明在函式中一個
局部變數 int b; 系統自動在棧中為b開闢空間
heap:
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = new char[10];//(char *)malloc(10);
但是注意p1、p2本身是在棧中的。
申請回響 棧:只要棧的剩餘空間大於所申請空間,系統將為程式提供記憶體,否則將報異常提示
棧溢出 。
堆:首先應該知道作業系統有一個記錄空閒
記憶體地址 的
鍊表 ,當系統收到程式的申請時,會遍歷該鍊表,尋 找第一個空間大於所申請空間的堆結點,然後將
該 結點從空閒結點鍊表中刪除 ,並將該結點的空間分配給程式,另外,對於
大多數系統 ,會在這塊記憶體空間中的
首地址處記錄本次分配的大小 ,這樣,代碼中的delete語句才能正確的釋放本記憶體空間。另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒
鍊表 中。
申請限制 棧:在Windows下,棧 是向低地址擴展 的數據結構,是一塊連續的記憶體的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向
高地址擴展 的數據結構,是不連續的記憶體區域。這是由於系統是用
鍊表 來存儲的空閒
記憶體地址 的,自然是不連續的,而
鍊表的遍歷方向是由低地址向高地址 。堆的大小受限於計算機系統中有效的
虛擬記憶體 。由此可見,堆獲得的空間比較靈活,也比較大。
效率比較 棧由系統自動分配,速度較快。但程式設計師是無法控制的。
堆是由new分配的記憶體,一般速度比較慢,而且容易產生
記憶體碎片 ,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配記憶體,他不是在堆,也不是在棧,而是直接在進程的
地址空間 中保留一塊記憶體,雖然用起來最不方便。但是速度快,也最靈活
存儲內容 棧: 在函式調用時,在大多數的C
編譯器 中,參數是由
右往左入 棧的,然後是函式中的
局部變數 。注意
靜態變數 是不入棧的。
當本次
函式調用 結束後,局部變數先
出棧 ,然後是參數,最後棧頂
指針 指向函式的返回地址,也就是
主函式 中的下一條指令的地址,程式由該點繼續運行。
堆:一般是在堆的頭部用一個位元組存放堆的大小。堆中的具體內容由程式設計師安排。
存取比較 char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運行時刻賦值的;
而bbbbbbbbbbb是在編譯時就確定的;
但是,在以後的存取中,在棧上的
數組 比
指針 所指向的字元串(例如堆)快。
比如:
#includevoid main(){char a = 1;char c[] = "1234567890";char *p ="1234567890";a = c[1];a = p[1];return;} 對應的彙編代碼
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一種在讀取時直接就把字元串中的元素讀到
暫存器 cl中,而第二種則要先把
指針 值讀到edx中,再根據edx讀取字元,顯然慢了。
小結 堆和棧的區別可以用如下的比喻來看出:
使用棧就象我們去飯館裡吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。
使用堆就象是自己動手做喜歡吃的菜餚,比較麻煩,但是比較符合自己的口味,而且自由度大。
主要分別 作業系統方面的堆和棧,如上面說的那些。
還有就是數據結構方面的堆和棧,這些都是不同的概念。這裡的堆實際上指的就是(滿足堆性質的)優先佇列的一種數據結構,第1個元素有最高的優先權;棧實際上就是滿足後進先出的性質的數學或數據結構。
雖然堆疊,堆疊的說法是連起來叫,但是他們還是有很大區別的,連著叫只是由於歷史的原因。
堆與棧的分布
補充說明 堆疊是一種存儲部件,即數據的寫入跟讀出不需要提供地址,而是根據寫入的順序決定讀出的順序。
形象來說,棧就是一條流水線,而流水線中加工的就是方法的主要程式,在分配棧時,由於程式是自上而下
順序執行 ,就將程式指令一條一條壓入棧中,就像流水線一樣。而堆上站著的就是工作人員,他們加工流水線中的商品,由程式設計師分配:何時加工,如何加工。而我們通常使用new
運算符 為對象在堆上分配記憶體(C#,Java),堆上尋找對象的任務交給
句柄 ,而棧中由棧
指針 管理