.LC0 標記的字串常數位於靜態分配區(.rodata),程式載入時即固定;堆疊則是執行期動態增長/縮減的區域,用於函數參數傳遞、區域變數、回傳位址暫存。
堆疊採用 LIFO(後進先出),若無基址偏址機制,只能順序存取。指令 pushl/popl 自動調整 %esp。
movl:資料搬移addl:加法subl:減法語法:指令 來源 目的(AT&T 風格)。
main:
leal 4(%esp), %ecx # 1. 暫存 argc 位址
andl $-16, %esp # 2. 堆疊 16-byte 對齊
pushl -4(%ecx) # 3. 壓入 argc (原始堆疊頂)
pushl %ebp # 4. 保存呼叫者 %ebp
movl %esp, %ebp # 5. 建立本幀 %ebp
pushl %ecx # 6. 保存 argc 位址到新幀
subl $4, %esp # 7. 分配/對齊區域空間
movl $.LC0, (%esp) # 8. 參數 "hello,world" 位址入棧
call puts # 9. 呼叫 puts (破壞寄存器)
movl $0, %eax # 10. 設定回傳值 0
addl $4, %esp # 11. 釋放區域空間
popl %ecx # 12. 還原 argc 位址
popl %ebp # 13. 還原呼叫者 %ebp
leal -4(%ecx), %esp # 14. 還原原始 %esp
ret # 15. 返回
call 隱含 pushl %eip(回傳位址)並跳轉。ret 彈出回傳位址跳轉。呼叫 puts 可能修改 %eax, %ecx, %edx 等「呼叫者保存」寄存器。編譯器在呼叫前將需保留的值(如 %ebp, %ecx)壓入堆疊,呼叫後彈出還原,這就是「保存現場」與「恢復現場」。
gcc -O2 -S hello.c # 會省略堆疊幀、合併指令、使用寄存器傳參
開啟優化後,main 可能縮減為:
main:
subl $12, %esp
movl $.LC0, (%esp)
call puts
xorl %eax, %eax
addl $12, %esp
ret