2017-07-22 145 views
0

我想更好地理解棧上的項目以及如何解決它們。我發現here的文章似乎表明,當MIPS堆棧被初始化時,將分配固定數量的內存,並且堆棧增長到堆棧限制,這看起來是較小的地址。我會假設基於這種邏輯,當遍歷0x0000時會發生堆棧溢出?關於堆棧增長和尋址的困惑

我意識到MIPS是大端,但這是否會改變棧的增長?我寫了我認爲可以在x86_64機器上觀察到這種情況的一種快速方法,但堆棧似乎正在成長,正如我原先假定的那樣。

#include <iostream> 
#include <vector> 
int main() { 
    std::vector<int*> v; 
    for(int i = 0; i < 10; i++) { 
     v.push_back(new int); 
     std::cout << v.back() << std::endl; 
    } 
} 

我還感到困惑,並不是所有的內存地址的的不出現是連續的,這讓我覺得我做了一些愚蠢的。有人可以澄清嗎?

+3

該程序在堆上分配數據,而不是在堆棧上分配數據。 – interjay

+0

哦,是因爲我使用矢量這個事實嗎?會使用字符數組在堆棧上? – mreff555

+2

矢量的數據和來自'new int'的分配都在堆上。只有本地變量本身(不包括任何內部分配的數據,例如向量的)在堆棧中。 – interjay

回答

1

本質上有3種類型的內存用於編程:靜態,動態/堆棧和堆棧。

靜態內存由編譯器預先分配,由程序中靜態聲明的常量和變量組成。

堆是,你可以自由地分配和釋放

堆棧內存是其獲取在函數聲明的所有局部變量分配的內存。這很重要,因爲每次調用函數時都會爲其變量分配一個新的內存。所以,每次調用函數都會確保它有自己獨特的變量副本。每次從函數返回時,內存都被釋放。

只要遵循上述規則,堆棧的管理方式絕對沒有關係。然而,將程序存儲器分配到較低的地址空間併成長是很方便的,並且堆棧要從最高的存儲器空間開始並長大。大多數系統實施這個方案。

通常有一個堆棧指針寄存器/變量指向當前堆棧地址。當一個函數被調用時,它會通過它的變量需要的字節數來減少這個地址。當它調用下一個函數時,這個新函數將從調用者已經減少的新指針開始。當函數返回時,它恢復它從其開始的指針。

可能會有不同的計劃,但據我所知,mips和i86會跟隨這個計劃。

而本質上,程序中只有一個虛擬內存空間。這取決於操作系統和/或編譯器如何使用它。編譯器會將邏輯區域中的內存拆分以供自己使用,並根據平臺文檔中定義的調用約定來處理它們。

因此,在我們的示例中,vi分配在功能堆棧上。 cout是靜態的。每個new int在堆中分配空間。 v不是一個簡單的變量,而是一個包含需要管理列表的字段的結構。它需要所有這些內部空間。因此,每個push_back修改這些字段以某種方式指向分配的「int」。 push_back()和back()是函數調用,併爲內部變量分配自己的堆棧,以避免干擾頂層函數。

2

x86機器上的堆棧也向下增長。字節順序與堆棧增長的方向無關。

機器的堆棧與std::vector<>完全無關。此外,new int分配堆內存,所以它告訴你絕對沒有關於堆棧。

爲了在堆棧增長的方向看,你需要做的是這樣的:

recursive(5); 

void recursive(int n) 
{ 
    if(n == 0) 
     return; 
    int a; 
    printf("%p\n", &a); 
    recursive(n - 1); 
} 

(請注意,如果你的編譯器是足夠聰明的優化尾遞歸,那麼你需要告訴它到不是優化它,否則觀測將全部錯誤。)

+0

x86_64整數我假設在我的例子中地址的差異應該是32位或0x20,但是當我編譯和運行程序時,大部分地址是32位,但不是全部。 – mreff555

+1

@ mreff555:這是因爲堆棧中還有其他東西,例如函數的返回地址。堆棧通常是對齊的,所以它可能需要額外的填充。而且,在這裏忘記一下:)當我們談論內存地址之間的差異時,我們用字節計算。 – geza

+0

這是有道理的,但我被告知我寫的是在堆上。無論如何,我想這是有道理的。我只是認爲載體是連續的。我只用了一點點,因爲我只是在腦海中做了一個快速轉換。 – mreff555