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