在這個簡單的函式中,為區域變數分配了空間。然后,變數被初始化并被printf呼叫以輸出它們。
000000000040056a <func>:
40056a: 55 push rbp ; function prologue
40056b: 48 89 e5 mov rbp,rsp ; function prologue
40056e: 48 83 ec 10 sub rsp,0x10 ; deallocating space for local variables
400572: 8b 4d fc mov ecx,DWORD PTR [rbp-0x4] ; variable initialization
400575: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] ; variable initialization
400578: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] ; variable initialization
40057b: 89 c6 mov esi,eax ; string stuff
40057d: bf 34 06 40 00 mov edi,0x400634 ; string stuff
400582: b8 00 00 00 00 mov eax,0x0 ; return value
400587: e8 84 fe ff ff call 400410 <printf@plt> ; printf()
40058c: c9 leave ; clean up local variables, pops ebp
40058d: c3 ret ; return to the address that was pushed onto the stack (by popping it into eip)
讓我困惑的是這條線sub rsp,0x10。程式如何知道分配 0x10 位元組?是猜測嗎?程式是事先決議好的嗎?
uj5u.com熱心網友回復:
編譯器之所以知道,是因為它查看了源代碼(或者實際上是在決議它之后對邏輯的內部表示)并將它必須為其分配堆疊空間的所有事物所需的總大小相加。并且它還必須在 之前獲得 RSP 16 位元組對齊call,因為函式入口上的 RSP % 16 == 8 。
所以對齊是編譯器可能保留比函式實際使用的更多的原因之一,但編譯器錯過的優化錯誤也會使其浪費空間:GCC 浪費額外的 16 位元組是很常見的,盡管這里沒有發生這種情況。
是的,現代編譯器在為它發出任何代碼之前決議整個函式(實際上是整個源檔案)。這就是提前優化編譯器的意義所在,因此即使您進行了除錯構建,它也是圍繞這樣做而設計的。相比之下,Tiny C 編譯器 TCC 是一次性的,它會在其函式序言中留下一個位置,以便稍后回傳并在源代碼中到達函式底部后填寫任何總大小。請參閱Tiny C 編譯器生成的代碼發出額外的(不必要的?)NOP 和 JMP - 當該數字恰好為零時,仍然sub esp, 0存在。(TCC 僅針對 32 位模式。)
相關:C 中的函式序言和結語
在葉函式中,編譯器可以在針對 x86-64 System V 時使用 RSP 下方的紅色區域,從而避免保留盡可能多(或任何)堆疊空間的需要,即使他們選擇溢位/重新加載一些區域變數。(例如,在未優化的代碼中根本沒有。)另見為什么這個函式序言中沒有“sub rsp”指令以及為什么函式引數存盤在負 rbp 偏移量?除了內核代碼,或其他用-mno-red-zone.
或者在 Windows x64 中,呼叫者需要保留影子空間供被呼叫者使用,這也使小函式有機會不花費任何指令移動 RSP,只需使用其回傳地址上方的影子空間。但是對于非葉函式,這意味著保留至少 32 位元組的影子空間以及任何用于對齊或區域變數的空間。參見示例陰影空間示例
在 x86-64 以外的 ISA 的標準呼叫約定中,其他規則可能會影響事物。
請注意,在 64 位代碼中,leave彈出 RBP,而不是 EBP,并且ret彈出到 RIP,而不是 EIP。
還有,mov ecx,DWORD PTR [rbp-0x4]不是variable initialization。 這是一個負載,從未初始化的記憶體到暫存器。可能你做了類似int a,b,c;沒有初始化器的事情,然后將它們作為引數傳遞給 printf。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/418159.html
標籤:
