像其他人一樣說,這取決於目標和compliler的,但對你的那些保持不變,而且沒有在代碼的任何其他東西,看起來像他們引入隨機性堆棧(相對說話),所以它每次都會做同樣的事情。
系統堆棧通常從高地址向低地址增長。如果堆棧指針是0x1234並且你推入一個值(在32位{4字節}系統上),那麼堆棧指針將變爲0x1230。
數組從最低地址到最高地址進行尋址。如果您有
char a[2];
並且[0]位於0x0122,則[1]將位於0x0123。
doit2
中的數組是一個自動變量,意味着它在創建函數時創建,並在函數退出時刪除。自動變量必須存在於堆棧或寄存器中。由於它是一個數組,因此編譯器將其放入RAM而不是寄存器會複雜得多(這使索引變得更容易,因爲它只是將索引*大小添加到數組第一個成員的地址)。由於堆棧在RAM中,因此編譯器會將數組放入堆棧。
爲該數組分配堆棧空間意味着堆棧指針爲sizeof(int)*16
,比如果該數組不存在時小。堆棧指針最有可能指向overflowme[0]
而在doit2
。
還有其他的東西可能在堆棧上,還有一些東西必須放在堆棧上。必須在堆棧上的東西是在函數被調用時被推到那裏的返回指針。在32位系統上,每個應占用4個字節。可能在堆棧上的東西(如果編譯器想要使用它的話)是前一幀指針。 (顯式*)x86-32上的堆棧幀僅僅是ESP和EBP之間的空間,但它們並不是必需的,因此它們通常不被使用,而EBP只是用作通用寄存器(可用的更多通用寄存器是一般很好)。 儘管使用堆棧幀很有用,因爲它們使調試變得更加容易,因爲ESP和EBP充當本地變量邊緣的標記。有時需要堆棧幀,例如當您使用alloca
或C99的可變大小的自動數組時,因爲它們允許函數的局部變量空間被mov EPB, ESP
或等效的instructiosn而不是sub size_of_local_variable, ESP
丟棄,所以編譯器不必知道大小的框架。它們還允許局部變量相對於EBP被尋址,而不是在alloca
的情況下變化的ESP。在這種情況下,EBP直到當前函數結束纔會更改,除非通過調用函數進行更改和恢復。
編譯時沒有優化打開編譯器通常總是使用堆棧幀,因爲它們使調試更容易。使用堆棧框架對代碼進行建模,然後在證明它們不是必需的之後,將代碼轉換爲不使用它們也更容易。
所以,EBP的前值可能會或可能不會駐留在返回地址(在problem2
某處),並在doit2
的overflowme
最後一個元素之間的堆棧。編譯器也可以自由地將任何其他東西放在堆棧上,所以誰知道還有什麼可以在那裏。
problem2
的局部變量int x
可以進入寄存器或棧中。在沒有優化的情況下編譯時,局部變量通常會進入堆棧,即使它們可以進入寄存器。
所以,讓我們假設有doit2
的overflowme
陣列,舊的幀指針,返回地址,並problem2
「堆疊在S x
(並且在某些更多的東西{這是真正在較高地址}它)。
由於&(overflowme[i])
相同的overflowme
第一元素的地址的加法,以(I * {的int
大小})和舊EBP位於的overflowme
的最後一個元素和一個返回地址後的舊後位於EBP和int x
位於返回地址之後,x
肯定會被緩衝區溢出的方式阻止。
爲什麼發生這種情況對於37的指數還不清楚。指針數學(假設只有上面列出的數據位於數組和x
之間的堆棧中)並不表明它應該基於4字節指針(32位機器),但如果這是8字節指針系統( 64位機器),那麼數學更接近我期望x
的地址,如果是sizeof(int) == 8
。編譯器也可以繼續前進,併爲printf
(格式字符串必須放在堆棧中後的可變參數)分配堆棧空間,這會影響數學運算(也鼓勵編譯器將x
放置在堆棧上,因爲它會無論如何都必須推它)。
如果您想要更詳細地回答您的問題,請查看此代碼的程序集並計算出確切的地址。
- 即使沒有使用EBP作爲幀基本指針,也可以認爲堆棧幀存在,但是幀不會被分幀。
不,每次37個作品! – user133466 2010-05-21 22:26:56
這是功課嗎? – 2010-05-21 22:27:31
它不一定 - 這隻會發生在特定的編譯器,CPU,編譯器標誌等。 – 2010-05-21 22:27:43