2014-02-28 32 views
3

彙編可以將數據存儲在寄存器或堆棧中。只有堆棧的頂部可以在任何時候訪問(對吧?)。考慮下面的C代碼:組件如何訪問/存儲堆棧中的變量

main(){ 
    int x=2; 
    func(); 
} 

func(int x){ 
    int i; 
    char a; 
} 

當打電話FUNC()被壓入堆棧以下(考慮32位系統):

variable x (4 bytes, pushed by main) 
<RETURN ADDRESS> (4 bytes pushed by main?) 
<BASE POINTER> (4 bytes pushed by func()) 
variable i (4 bytes, pushed by func()) 
variable a (1 byte, pushed by func()) 

我有以下問題:

  1. 在C代碼中,您可以從函數內的任何位置訪問本地變量,但是在彙編中,您只能訪問堆棧的頂部。 C代碼被翻譯成彙編(在機器代碼中,但彙編是它的可讀形式)。那麼程序集如何支持讀取不在棧頂的變量?

  2. 在我的例子中,我是否遺漏了任何會被推入堆棧的東西?

  3. 在程序集中,如果你推動堆棧上的一個char或一個int,它如何確定它需要推送4個字節還是1個字節?因爲它使用相同的操作(推)正確嗎?

在此先感謝 Gr。 Maricruzz

回答

2

編譯器正在生成程序集,每個指令集可能不同,但在一天結束時,堆棧只是一個寄存器,它持有一個地址到內存。編譯器正在創建並且知道它正在創建的函數的整個範圍,並且知道在本地數據項的堆棧中找到每個數據項有多遠,因此它會根據指令集創建適當的代碼來訪問這些本地數據項項目。一些指令集需要複製堆棧指針和/或用堆棧指針作爲操作數做數學運算,然後根據該數學運算得到其他一些寄存器,然後根據該數學運算(堆棧指針+8單詞)例如)您訪問該內存地址。一些指令集有一個尋址模式,您可以在加載或存儲時向棧指針施加偏移量,數學運算作爲指令執行的一部分完成,您不必使用中間結果和寄存器。

1

只有堆棧的頂部可以在任何時候進行訪問(右?)

沒有,一般的ISA有說明訪問堆棧上的其他元素。也就是說,訪問堆棧中的元素不限於pushpop類似的操作;通常你可以在堆棧位置和寄存器之間來回穿梭mov

3

將函數開頭的堆棧指針放入寄存器,然後通過此基址加上變量的偏移量訪問變量/參數。

如果您想查看代碼而不是創建目標文件,請讓編譯器停止創建彙編程序文件。然後你可以看到它究竟是如何工作的。 (當然,這需要你有一個有效的C程序,不像你現在問題中的那個)

+0

謝謝!是否也有可能在某處看到堆棧的當前內容?任何工具呢? – Maricruzz

+0

@Maricruzz任何體面的調試器都應具有該功能。 –

1

Assembly可以通過地址訪問任何內存(就像C)。

簡單的未優化程序會在方法執行前將所有本地變量放在堆棧上,所以變量地址是執行幀的地址加上一些移位。

然後程序可以簡單地使用poppush方法來在堆棧頂部存儲附加變量(即一些表達式的子結果)。

總結:

  1. 有寄存器(ESPx86)指向堆棧
  2. 調用push移動變量到堆棧的頂部和增加的頂部這個寄存器
  3. 調用pop從堆棧頂部移動變量並減少該寄存器
  4. 調用mov正在存儲器和寄存器之間移動變量,並且執行沒有任何要堆棧寄存器(ESP)。
+0

按操作數的大小esp遞減,將操作數存儲在[esp]。彈出從[esp]加載操作數,然後按操作數的大小增加esp。在禁用「幀指針」的情況下,C會生成使用esp中的偏移量來定位變量的代碼。啓用「幀指針」後,函數會執行esp | mov ebp,esp和基於ebp的大部分偏移量。 – rcgldr