編寫有效的代碼需要了解堆棧和堆內存,這使其成為學習編程的重要組成部分。不僅如此,新程序員或職場老手都應該完全熟悉堆棧內存和堆內存之間的區別,以便編寫有效且優化的代碼。
這篇博文將對這兩種內存分配技術進行全面的比較。通過本文的結論,我們將對堆棧和堆內存有一個透徹的了解,從而使我們能夠在編程工作中有效地使用它們。
對比理解堆棧與堆的結構!
內存分配
內存是計算機編程的基礎。它提供了存儲數據和程序高效運行所需的所有命令的空間。分配內存可以與在計算機內存中為特定目的指定特定區域進行比較,例如容納對程序功能至關重要的變量或對象。程序的內存布局和組織可能會根據所使用的操作系統和體系結構而有所不同。然而,一般來說,內存可以分為以下幾個部分:
全局段(Global segment)
代碼段(Code segment)
堆棧(Stack)
堆(Heap)
全局段,負責存儲全局變量和靜態變量,這些變量的生命周期等于程序執行的整個持續時間。
代碼段,也稱為文本段,包含組成我們程序的實際機器代碼或指令,包括函數和方法。
堆棧段,用于管理局部變量、函數參數和控制信息(例如返回地址)。
堆段,提供了一個靈活的區域來存儲大型數據結構和具有動態生命周期的對象。堆內存可以在程序執行期間分配或釋放。
注意:值得注意的是,內存分配上下文中的堆棧和堆不應與數據結構堆棧和堆混淆,它們具有不同的用途和功能。
四個內存段(全局、代碼、堆棧和堆)的概述,說明了堆向下增長和堆棧向上增長的常規表示
每個程序都有自己的虛擬內存布局,由操作系統映射到物理內存。每個細分市場的具體分配取決于多種因素,例如:
程序代碼的大小。
全局變量的數量和大小。
程序所需的動態內存分配量。
程序使用的調用堆棧的大小。
在任何函數外部聲明的全局變量都將駐留在全局段中。程序功能和方法的機器代碼或指令將存儲在代碼段中。讓我們看一下編碼示例,以幫助可視化全局和代碼段在內存中的使用方式:
?
public class Main { // Global Segment:全局變量存放在這里 static int globalVar = 42; // 代碼段:這里存放函數和方法 public static int add(int a, int b) { return a + b; } public static void main(String[] args) { // 代碼段:調用add函數 int sum = add(globalVar, 10); System.out.println("Sum: " + sum); } }
?
Java 中的全局和代碼段
globalVar在這些代碼示例中,我們有一個值為 的全局變量42,它存儲在全局段中。我們還有一個函數add,它接受兩個整數參數并返回它們sum;該函數存儲在代碼段中。該main函數(或 Python 中的腳本)調用該add函數,傳遞全局變量和另一個整數值10作為參數。
代碼中的全局和代碼段(未顯示堆和堆棧段)
需要強調的是,管理堆棧和堆段對于代碼的性能和效率起著重要作用,使其成為編程的一個重要方面。因此,程序員在深入研究它們的差異之前應該充分理解它們。
棧內存:有序存儲
將堆棧內存視為有組織且高效的存儲單元。它使用后進先出 (LIFO) 方法,這意味著最近添加的數據將首先被刪除。內核是操作系統的核心組件,自動管理堆棧內存;我們不必擔心分配和釋放內存。當我們的程序運行時,它會自行處理。
下面不同編程語言的代碼實例演示了堆棧在各種情況下的使用。
?
public class StackExample { // 一個簡單的函數來添加兩個數字 public static int add(int a, int b) { // 局部變量(存儲在棧中) int sum = a + b; return sum; } public static void main(String[] args) { // 局部變量(存儲在棧中) int x = 5; // 函數調用(存儲在堆棧中) int result = add(x, 10); System.out.println("Result: " + result); } }
?
Java 中的堆棧內存使用:演示局部變量和函數調用
調用函數時會創建稱為堆棧幀的內存塊。堆棧幀存儲與局部變量、參數和函數的返回地址相關的信息。該內存是在堆棧段上創建的。
在上面的代碼實例中,我們創建了一個名為 的函數add。該函數采用兩個參數作為輸入整數并返回它們的sum. 在函數內部add,我們創建了一個局部變量調用sum來存儲結果。該變量存儲在堆棧內存中。
在main函數(或 Python 的頂級腳本)中,我們創建另一個局部變量x并為其分配值5。該變量也存儲在堆棧內存中。x然后,我們以和作為參數調用 add 函數10。函數調用及其參數和返回地址都放置在堆棧中。一旦add函數返回,堆棧就會被彈出,刪除函數調用和關聯的數據,我們可以打印結果。
在下面的解釋中,我們將介紹運行每行重要代碼后堆和堆棧如何變化。盡管我們用的的是 C++,但對 Python 和 Java 的解釋也同樣適用。我們在這里只討論堆棧段。
堆棧段為空
1共 9 個
為主函數創建一個新的堆棧幀
2共 9 個
在 main 函數的堆棧幀中,局部變量 x 現在的值為 5
3共 9 個
調用 add 函數,實際參數為 (5, 10)
4共 9 個
控制權轉移到 add 函數,為 add 函數創建一個新的堆棧幀,其中包含局部變量 a、b 和 sum
5共 9 個
add 函數的堆棧幀上的 sum 變量被分配 a + b 的結果
6共 9 個
add 函數完成其任務并且其堆棧幀被銷毀
7共 9 個
具有可變結果的主函數的堆棧幀存儲從 add 函數返回的值
8共 9 個
在顯示結果值(此處未顯示)后,主功能塊也被銷毀,并且堆棧段再次為空
9共9 個
以下是 C++ 代碼按執行順序的解釋:
第 10 行:程序從該main函數開始,并為其創建一個新的堆棧幀。
第 12 行:局部變量x被賦值為5。
第 15 行:add使用參數x和調用該函數10。
第 4 行:為該函數創建一個新的堆棧幀add。控制權轉移到add帶有局部變量的函數。a、b、 和sum。變量a和分別被賦予和b的值。x10
第 6 行:局部變量sum被賦值為a + b(即 5 + 10)。
第 7 行:變量sum的值(即 15)被返回給調用者。
第 8 行:add從堆棧中彈出函數的堆棧幀,并釋放所有局部變量(、和a)?b。sum
第15行:result函數堆棧幀上的局部變量main被賦予返回值(即15)。
第 17 行:存儲在變量中的值result(即 15)使用 打印到控制臺std::cout。
第 19 行:函數main返回 0,表示執行成功。
第 20 行:函數main的堆棧幀從堆棧中彈出,并且所有局部變量 (x和result) 都被釋放。
堆棧存儲器的主要特點
以下是有關堆棧內存需要考慮的一些關鍵方面:
固定大小:當涉及到堆棧內存時,其大小保持固定,并在程序執行開始時確定。
速度優勢:堆棧內存幀是連續的。因此,在堆棧內存中分配和釋放內存的速度非常快。這是通過操作系統管理的堆棧指針對引用進行簡單調整來完成的。
控制信息和變量的存儲:堆棧內存負責容納控制信息、局部變量和函數參數,包括返回地址。
有限的可訪問性:請務必記住,存儲在堆棧內存中的數據只能在活動函數調用期間訪問。
自動管理:堆棧內存的高效管理由系統本身完成,不需要我們額外的工作。
堆內存:動態存儲
堆內存,也稱為動態內存,是內存分配的野孩子。程序員必須手動管理它。堆內存允許我們在程序執行期間隨時分配和釋放內存。它非常適合存儲大型數據結構或大小事先未知的對象。
下面不同編程語言的代碼實例演示了堆的使用。
?
public class HeapExample { public static void main(String[] args) { // 棧:局部變量“value”存儲在 棧中 int value = 42; // 堆:為堆上的單個 Integer 分配內存 Integer ptr = new Integer(value); // 將值分配給分配的內存并打印它 System.out.println("Value: " + ptr); // 在Java中,垃圾收集是自動的,因此不需要 釋放內存 } }
?
演示 Java 中的堆內存分配和使用
在這些代碼示例中,目標是將值存儲42在堆內存中,這是一個更永久、更靈活的存儲空間。這是通過使用駐留在堆棧內存中的指針或引用變量來完成的:
int* ptr在C++中。
Java 中的一個Integer對象ptr。
ptrPython 中包含單個元素的列表。
然后打印存儲在堆上的值。在C++中,需要使用delete關鍵字手動釋放堆上分配的內存。然而,Python 和 Java 通過垃圾收集自動管理內存釋放,無需手動干預。
注意:在 Java 和 Python 中,垃圾收集會自動處理內存釋放,無需手動釋放內存,如 C++ 中所示。
在下面的解釋中,我們將討論運行每行重要代碼后堆和堆棧如何變化。盡管我們關注的是 C++,但該解釋也適用于 Python 和 Java。我們在這里只討論堆棧和堆段。
棧段和堆段為空
1共 7 個
為主函數創建一個新的堆棧幀
2共 7 個
局部變量值被賦予值 42
3共 7 個
在堆上分配了一個指針變量ptr,指針ptr中存放的是分配的堆內存的地址(即0x1000)
4共 7 個
value變量中存儲的值(即42)被賦值給ptr指向的內存位置(堆地址0x1000)
5共 7 個
堆上地址 0x1000 處分配的內存被釋放
6共 7 個
main函數的棧幀從棧中彈出(顯示result的值后),棧段和堆段再次清空
7共7 個
以下是 C++ 代碼按執行順序的解釋:
第 3 行:main調用該函數,并為其創建一個新的堆棧幀。
第 5 行:堆棧幀上的局部變量value被賦值為42。
第 8 行:ptr使用關鍵字為堆上的單個整數動態創建的內存分配給指針變量new。我們假設堆上新內存的地址為 0x1000。分配的堆內存的地址(0x1000)存儲在指針中。ptr。
第 11 行:將整數值42分配給ptr(堆地址 0x1000)所指向的內存位置。
第 12 行:(ptr?)指向的內存位置存儲的值42被打印到控制臺。
第 15 行:使用關鍵字釋放在堆上地址 0x1000 處分配的內存delete。在此行之后,ptr成為懸空指針,因為它仍然保存地址 0x1000,但該內存已被釋放。然而,對于這個重要的討論,我們不會詳細討論懸空指針。
第17行:?main函數返回0,表示執行成功。
第 18 行:從堆棧中彈出主函數的堆棧幀,并釋放所有局部變量 (value和)。ptr
注意:C++ 標準庫還提供了一系列智能指針,可以幫助自動化堆中內存分配和釋放的過程。
堆內存的主要特點
以下是需要記住的堆內存的一些顯著特征:
大小的靈活性:堆內存大小可以在程序執行過程中發生變化。
速度權衡:在堆中分配和釋放內存速度較慢,因為它涉及尋找合適的內存幀和處理碎片。
動態對象的存儲:堆內存存儲具有動態生命周期的對象和數據結構,如newJava 或 C++ 中使用關鍵字創建的對象和數據結構。
持久數據:存儲在堆內存中的數據將一直保留在那里,直到我們手動釋放它或程序結束。
手動管理:在某些編程語言(例如C和C++)中,必須手動管理堆內存。如果處理不當,可能會導致內存泄漏或資源使用效率低下。
堆棧與堆:差異對比
現在我們徹底了解了堆棧和堆內存分配的工作原理,我們可以區分它們了。在比較棧內存和堆內存時,我們必須考慮它們的獨特特性來理解它們的差異:
大小管理:堆棧內存具有在程序執行開始時確定的固定大小,而堆內存是靈活的,可以在程序的整個生命周期中更改。
速度:堆棧內存在分配和釋放內存時具有速度優勢,因為它只需要調整引用。相反,由于需要定位合適的內存幀并管理碎片,堆內存操作速度較慢。
存儲目的:堆棧內存指定用于控制信息(例如函數調用和返回地址)、局部變量和函數參數(包括返回地址)。另一方面,堆內存用于存儲具有動態生命周期的對象和數據結構,例如newJava 或 C++ 中使用關鍵字創建的對象和數據結構。
數據可訪問性:堆棧內存中的數據只能在活動函數調用期間訪問,而堆內存中的數據在手動釋放或程序結束之前仍然可以訪問。
內存管理:系統自動管理堆棧內存,優化其使用,以實現快速高效的內存引用。相比之下,堆內存管理是程序員的責任,處理不當可能會導致內存泄漏或資源使用效率低下。
下表總結了堆棧內存和堆內存在不同方面的主要區別:
方面對比 | 堆棧內存 | 堆內存 |
尺寸管理 | 固定大小,在程序開始時確定 | 靈活的大小,可以在程序的生命周期中改變 |
速度 | 更快,只需要調整一個參考 | 速度較慢,涉及定位合適的塊和管理碎片 |
儲存目的 | 控制信息、局部變量、函數參數 | 具有動態生命周期的對象和數據結構 |
數據可訪問性 | 僅在活動函數調用期間可訪問 | 在手動釋放或程序結束之前均可訪問 |
內存管理 | 由系統自動管理 | 由程序員手動管理 |
堆棧內存與堆內存:何時使用每種類型
我們現在知道堆棧內存和堆內存之間的區別。現在讓我們看看何時使用每種類型的內存。
堆棧是 C++、Java 和 Python 中存儲局部變量和函數參數的默認選項,其生命周期較短且可預測。但在以下情況下建議使用堆內存:
當需要存儲對象、數據結構或動態分配的數組時,其生命周期在編譯時或函數調用期間無法預測。
當內存需求很大或者我們需要在程序的不同部分之間共享數據時。
當需要分配超出單個函數調用范圍的內存時。
此外,C++ 中需要手動內存管理(使用delete),而在 Java 和 Python 中,內存釋放主要通過垃圾回收來處理。盡管如此,我們還是應該注意內存使用模式以避免出現問題。
結論
對于任何尋求編寫高效且優化的代碼的程序員來說,了解堆棧內存和堆內存之間的差異至關重要。
堆棧內存最適合臨時存儲、局部變量和函數參數。
堆內存非常適合大型數據結構和具有動態生命周期的對象。
我們需要謹慎選擇合適的內存分配方法;我們可以創建高效且性能良好的程序。
每種類型的內存都有其自己的一組功能,使用它們來確保我們軟件的性能和資源利用率至關重要。
評論