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