日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[程序设计语言] 堆和栈的全面总结

發布時間:2023/11/30 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [程序设计语言] 堆和栈的全面总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

操作系統堆棧:

??????? 分配由編譯器自己主動和自己主動釋放。對應于堆棧的函數。參數存儲功能值、函數調用結束后完成值和局部變量的函數體內。段內存空間。其操作和組織方式與數據結構中的棧十分相似。棧是為了運行線程留出的內存空間。當調用函數時創建棧。當函數運行完畢,棧就被回收了。

操作系統中的堆:

?? ????? 由程序猿手動進行內存的申請與釋放。因為程序猿手動申請及釋放的內存塊存放在堆中。堆中有非常多內存塊,所以堆的組織方式類似于鏈表。操作系統中的堆與數據結構中的堆全然不同。我認為通俗的理解能夠是這種:數據結構中的堆是"結構堆"。有嚴謹的邏輯和操作方式。而操作系統中的堆。更像是使用鏈表將"一堆雜亂的東西"聯系起來。堆是為動態分配預留的內存空間,其生命周期為整個應用程序的生命周期。

當應用程序結束以后,堆開始被回收。

??????? 每個線程都有一個屬于自己的棧,但每個應用程序通常僅僅有一個堆(一個應用程序使用了多個堆的情況也是有的)。當線程被創建的時候。設置了棧的大小。

在應用程序啟動的時候,設置了堆的大小。棧的大小一般是固定的,可是堆能夠在須要的時候進行擴展,如程序猿向操作系統申請很多其它內存的時候。

因為棧的工作方式類似于數據結構中的棧,堆的工作方式類似于鏈表,所以棧顯然會比堆快得多。依照棧的存取方式,想要釋放內存或是新增內存。僅僅須要對應移動棧頂指針就可以。堆則要首先在內存的空暇區域尋找合適的內存空間,然后占用。然后指向這塊空間。顯然堆比棧要復雜得多。

???????接下來本來是想將棧和堆分開進行陳述,斟酌了一下還是決定從同一方面對棧和堆進行比較。有了比較才明顯。

1. 在創建棧的時候棧的大小就固定了,由于棧要連續占用一段空間。依據上文所屬的堆的特性,決定了堆的大小是動態的,其分配和釋放也是動態的。

2. 棧中的數據過多會導致爆棧。比方dfs寫搓了。

而假如堆也爆了的話。。

。那說明內存也爆了。

3. 每一個函數的棧都是各自獨立的,可是一個應用程序的堆是被全部的棧共享。

既然提到共享,那么這里就有"并行存取"的問題了。實際上并行存取是由堆控制的。而不是被??刂频摹?/p>

4. 棧的作用域僅限于函數內部,棧在函數結束的時候會自行釋放掉空間??墒莿摻ㄓ诙焉系淖兞勘匦枰謩俞尫拧6阎械淖兞坎淮嬖谧饔糜虻膯栴}。由于堆是全局的。

5. 棧中存放的是函數返回值地址、函數參數。函數內的局部變量等。堆中存放的是由程序猿手動進行申請的內存塊(malloc、new等)。

6. 堆和棧都按需進行分配。棧有嚴格的容量上限。而堆的容量上限則是"不嚴格"的。堆并沒有固定的容量上限,它與當前的剩余內存量有關(事實上還不準確,操作系統還有虛擬內存或其它概念,所以堆的工作方式較為抽象)。


7. 通過移動棧頂指針就可以實現棧內存的分配。

在堆上分配內存的做法則是從當前空暇的內存中找一塊滿足大小的區域。就像鏈表的工作方式一樣。

8. 僅僅要沒有超出棧容量。棧能夠進行隨意的釋放和申請內存。并不會造成內存出現故障,是安全的。而堆不同,大量申請和釋放小內存塊可能會造成內存問題,這些小的內存塊零散的分布在內存中。導致興許大塊的內存申請失敗。由于盡管空暇的內存足夠多??墒遣⒉贿B續。這樣的情況下的小塊內存叫做"堆碎片"。只是這并非什么大問題。詳細詳見"操作系統"的有關知識。

9. 棧在確定了棧底地址后,其棧頂指針從棧底地址開始。逐漸向低地址走。也就是說棧的存儲空間是從高地址走向低地址的。

堆則相反,堆在申請空間的時候通常逐漸往高地址的方向來尋找可用內存。

純粹的文字描寫敘述顯得枯燥無味,我們來看一些代碼:

#include <iostream> using namespace std;void func() {int i = 5;int j = 3;int k = 7;int *p = &i;printf("%d\n", *p);printf("%d\n", *(p-1));printf("%d\n", *(p-2));}int main() {func();getchar();return 0; }上述代碼的結果是:5 3 7

從結果中我們能夠看出兩件事:

一是棧地址是連續的,我們能夠通過一個指針和一個相對的大小,來"偏移"到別的變量上去。

二是從中能夠看出棧地址是從高到低分布的,棧底在高地址。朝低地址的方向生長。

所以程序中是p-1而不是p+1。

void func() {int *p = NULL;// 上行代碼是個重點。這個指針待會會用于申請新的內存。// 此時除了它自身作為一個變量須要占用4字節的空間(指針都占4字節),沒有不論什么其它空間被申請。// 這個指針變量是函數的局部變量,所以它被創建在棧上。int num = 100; // 這個變量相同創建于棧上。int buffer[100]; // 相同的,buffer占用了棧的400字節的空間p = new int[100]; // 注意,程序猿手動申請了一塊空間,這400字節的內存創建于堆上。// 所以此刻p的狀態是:p為函數局部變量,它指向了一塊全局范圍的內存空間。 } // 函數體結束。上述函數有個嚴重的問題,那就是指針p的內存泄露。 // 正確的做法是在函數最后delete掉這塊內存?;蚴欠祷剡@塊內存的地址以供繼續使用。 接下來我們來了解一下當調用一個函數的時候所發生的事情:
首先操作系統為這個函數分配了一個棧。由于在調用完這個函數以后須要能正確返回到下一條語句并繼續運行,所以第一步是將調用完函數的下一條指令的地址壓入棧。這樣當函數調用完畢,棧頂指針一點點釋放內存以后。棧頂指針指向了這個地址,就能返回到正確的位置繼續運行了。

int main() {func();printf("%d\n", 100);return 0; }比方上述代碼,在調用func之前,首先把func的下一條語句,也就是printf語句的地址。存在棧中。

這樣函數調用完畢后就能正確返回到這個printf并繼續往后運行了。

注意這里的地址是指令地址,而不是變量地址什么的。它有那么點類似于操作系統中的程序計數器(PC,即Program Counter)。

然后把實參從右到左的順序依次入棧(大多數的C/C++編譯器為從右到左)接著是函數中的各種局部變量。要注意的是函數中的static變量是不入棧的。

全局變量和static變量在編譯的時候就已經在靜態存儲區分配好內存了。


假設這個時候該函數又調用了其他函數,過程也是一樣的。首先是返回地址,然后是參數和局部變量。

這樣在每層調用結束,棧頂指針不斷下降(釋放內存)的時候,就能正確返回到之前調用的位置并繼續往下運行了。
出棧?;蛘哒f釋放內存的過程。依據棧的特性,是相反的,所以就不贅述了。


一個 C或C++程序,它眼中的內存地址分分為這么五個區域:

棧區(stack)、堆區(heap)、全局靜態區(static)、文字常量區和程序指令區。

棧區和堆區前面已經介紹過。全局靜態區用于存放全局變量和靜態static靜態變量。全局靜態區分為兩塊內容:一塊用于初始化以后的全局變量和靜態變量,一塊用于未初始化的全局變量和靜態變量。全局靜態區和堆一樣,程序結束后由操作系統進行釋放。文字常量區用于存放常量字符串。程序結束后由操作系統進行釋放。

程序指令區最好理解,就是存放程序代碼的二進制指令。

int cnt; // 存放在全局靜態區的未初始化區 int num = 0; // 存放在全局靜態區的已初始化區 int *p; // 存放在全局靜態區的未初始化區 int main() {int i, j, k; // 存放在棧區int *pBuffer = (int *)malloc(sizeof(int) * 10); // 指針pBuffer在棧中,該內存在堆中char *s = "hactrox"; // 指針s存放在棧中,字符串存放在文字常量區中char str[] = "hactrox"; // str和字符串存放在棧中static int a = 0; // a存放在全局靜態區的已初始化區 }
char *s = "hactrox";?? ?// "hactrox"在文字常量區。s指向這個區域中的"hactrox",所以這能夠理解為。首先在文字常量區創建了這個字符串,然后s指向這個字符串這樣兩個步驟。

s本身作為一個局部變量存儲在棧中。
// 以下的代碼是錯誤的,指針還沒指向就直接賦值了?
int *p = 5;
// 以下的代碼才是正確的,首先要創建這個int型變量,然后p指向這個變量。new來的int變量在堆中。
int *p = new int(5);
接下來我們看一看一個很常見的問題:下述代碼有沒有什么問題?有問題的話問題在哪里?

#include <iostream> using namespace std;char* f1() {char *s = "hactrox";return s; }char* f2() {char s[] = "hactrox";return s; }int main() {printf("%s\n", f1());printf("%s\n", f2());getchar();return 0; }問題在于第二個函數,f2并不能正確返回那個字符串。在函數f1中。"hactrox"字符串創建于文字常量區。然后返回該常量字符串的地址,由于文字常量區的字符串是全局的,盡管指針s是局部變量,可是s在消亡前已經把目標地址送出來了。所以s消亡與否不是重點。重點是返回的地址所指向的區域還在,所以能正確顯示。在函數f2中。“hactrox”與s均為局部變量。它們保存在棧中。

盡管s相同返回了一個地址。但這個地址所指向的內存已經被釋放掉了。地址有效,但目標已無效。所以輸出的僅僅是亂碼。
關于文字常量區另一些東西要說(好像和本博客的主題扯遠了?)

#include <iostream> using namespace std;void func() {char *str1 = "123";printf("%x\n", str1);char *str2 = "123";// 同在文字常量區。編譯器可能會將str2直接指向str1所指向的內存。// 而不是開辟新的空間來存放第二個同樣字符串。// 通過打印str2的指針可驗證printf("%x\n", str2);char *s1 = "hactrox";printf("%x\n", s1);char *s2 = "hactrox";printf("%x\n", s2); } int main() {func();getchar();return 0; }char s[] = "hactrox";
char *s = "hactrox again";
第二段代碼,即文字常量區變量在編譯的時候就已經確定了,
而第一段代碼。是在執行的時候進行賦值的。
這樣看起來貌似第二段代碼的效率要高。事實上不然,
當在執行時刻用到這兩個變量的時候,對于第一段代碼。直接讀取字符串,而對于第二段代碼,首先讀取該字符串指針。然后依據指針再讀取字符串,顯然效率就下降了。

事實上我認為關注棧和堆,事實上主要是關注作用域、生命周期和有效性的問題。

指針被釋放了。不代表指針指向的內存會被釋放。相同的。指針指向的內存被釋放了,不代表指針會被同步釋放或自己主動指向NULL,指針依然指向那塊已經失效了的地址。這個地址不能用于。沒有人能保證較長的有效地址,接下來會發生什么。

版權聲明:本文博客原創文章,博客,未經同意,不得轉載。

轉載于:https://www.cnblogs.com/bhlsheji/p/4675742.html

總結

以上是生活随笔為你收集整理的[程序设计语言] 堆和栈的全面总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。